import requests
import json
import time
import re
import os
import urllib.parse
from flask import Flask, jsonify, request, send_from_directory, session
from flask_cors import CORS
from functools import wraps
import hashlib
import secrets
from datetime import datetime, timedelta

app = Flask(__name__, static_folder='.')
app.secret_key = secrets.token_hex(32)  # Chave secreta para sessões
CORS(app, supports_credentials=True)

# ============================================================
# CONFIGURACOES
# ============================================================
CONFIG = {
    'xweb_url': 'http://109.50.83.43',
    'username': 'User',
    'password': 'teste',
    'language': 'pt-BR',
    'device_ids': [36, 37],
    'plgid': '64154db3b3ce42cd8fe8cbe4332c4dea',
    'refresh_interval': 10,
    # Senha de segurança para o frontend
    'security_password': 'Serratec2026#',  # ALTERE PARA SUA SENHA
    'session_timeout': 3600  # 1 hora
}

# ============================================================
# DECORATOR DE AUTENTICAÇÃO
# ============================================================
def require_auth(f):
    """Decorator que exige autenticação para acessar o endpoint"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        # Verificar se o usuário está autenticado na sessão
        if not session.get('authenticated'):
            return jsonify({
                'success': False,
                'error': 'Autenticação necessária',
                'code': 'AUTH_REQUIRED'
            }), 401
        return f(*args, **kwargs)
    return decorated_function

def require_auth_optional(f):
    """Decorator que permite acesso com ou sem autenticação"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        # Verificar autenticação mas não bloquear
        is_authenticated = session.get('authenticated', False)
        # Passar o estado para a função via kwargs
        kwargs['is_authenticated'] = is_authenticated
        return f(*args, **kwargs)
    return decorated_function

# ============================================================
# MAPEAMENTO MANUAL DOS NOMES DOS PONTOS
# ============================================================
POINT_NAMES = {
    '36': {
        '1': {'name': '2- entree d eau glacee primaire', 'vgrp': 0, 'unit': '°C', 'editavel': False},
        '2': {'name': '1- Saída d eau glacee primaire', 'vgrp': 0, 'unit': '°C', 'editavel': False},
        '3': {'name': '4- Delta P index2', 'vgrp': 0, 'unit': 'bar', 'editavel': False},
        '4': {'name': '6- condensation circuit HP1', 'vgrp': 0, 'unit': 'bar', 'editavel': False},
        '5': {'name': '7- condensation circuit HP2', 'vgrp': 0, 'unit': 'bar', 'editavel': False},
        '7': {'name': '5- sortie du ballon pour vanne index', 'vgrp': 0, 'unit': '°C', 'editavel': False},
        '8': {'name': '8- depart eau glacee index', 'vgrp': 0, 'unit': '°C', 'editavel': False},
        '10': {'name': '3- Delta P index1', 'vgrp': 0, 'unit': 'bar', 'editavel': False},
        '55': {'name': '9- vitesse condenseur', 'vgrp': 0, 'unit': '%', 'editavel': False},
        '56': {'name': '10- vanne 3 voies index', 'vgrp': 0, 'unit': '%', 'editavel': True},
        '57': {'name': '11- vitesse pompe index', 'vgrp': 0, 'unit': '%', 'editavel': True},
        '73': {'name': '0- consigne actuelle reglee', 'vgrp': 0, 'unit': '°C', 'editavel': False},
        '106': {'name': '0.1- consigne eau glacee', 'vgrp': 0, 'unit': '°C', 'editavel': True},
        '113': {'name': 'InverterPump SetPoint User Side', 'vgrp': 0, 'unit': 'bar', 'editavel': True},
        '169': {'name': 'etat pressostat Haute pression 1', 'vgrp': 3, 'unit': '', 'editavel': False},
        '170': {'name': 'etat pressostat basse pression 1', 'vgrp': 3, 'unit': '', 'editavel': False},
        '171': {'name': 'etat pressostat Haute pression 2', 'vgrp': 3, 'unit': '', 'editavel': False},
        '172': {'name': 'etat pressostat basse pression 2', 'vgrp': 3, 'unit': '', 'editavel': False},
        '173': {'name': 'etat relais thermique comp 1/a', 'vgrp': 3, 'unit': '', 'editavel': False},
        '174': {'name': 'etat relais thermique comp 2/a', 'vgrp': 3, 'unit': '', 'editavel': False},
        '175': {'name': 'etat relais thermique comp 3/a', 'vgrp': 3, 'unit': '', 'editavel': False},
        '176': {'name': 'etat relais thermique comp 1/b', 'vgrp': 3, 'unit': '', 'editavel': False},
        '177': {'name': 'etat relais thermique comp 2/b', 'vgrp': 3, 'unit': '', 'editavel': False},
        '178': {'name': 'etat relais thermique comp 3/b', 'vgrp': 3, 'unit': '', 'editavel': False},
        '179': {'name': 'etat relais debit d eau primaire', 'vgrp': 3, 'unit': '', 'editavel': False},
        '180': {'name': 'etat pompe primaire 1', 'vgrp': 3, 'unit': '', 'editavel': False},
        '181': {'name': 'etat pompe primaire 2', 'vgrp': 3, 'unit': '', 'editavel': False},
        '182': {'name': 'etat relais thermique ventilocond', 'vgrp': 3, 'unit': '', 'editavel': False},
        '183': {'name': 'etat des pompes secondaires', 'vgrp': 3, 'unit': '', 'editavel': False},
        '188': {'name': 'etat controleur de phases', 'vgrp': 3, 'unit': '', 'editavel': False},
        '225': {'name': 'Compresseur 1 circuit A', 'vgrp': 4, 'unit': '', 'editavel': False},
        '226': {'name': 'Compresseur 2 circuit A', 'vgrp': 4, 'unit': '', 'editavel': False},
        '227': {'name': 'Compresseur 3 circuit A', 'vgrp': 4, 'unit': '', 'editavel': False},
        '228': {'name': 'Compresseur 1 circuit B', 'vgrp': 4, 'unit': '', 'editavel': False},
        '229': {'name': 'Compresseur 2 circuit B', 'vgrp': 4, 'unit': '', 'editavel': False},
        '230': {'name': 'Compresseur 3 circuit B', 'vgrp': 4, 'unit': '', 'editavel': False},
        '231': {'name': 'Pompe primaire 1', 'vgrp': 4, 'unit': '', 'editavel': False},
        '232': {'name': 'Pompe primaire 2', 'vgrp': 4, 'unit': '', 'editavel': False},
        '234': {'name': 'Pompe index', 'vgrp': 4, 'unit': '', 'editavel': False},
        '237': {'name': 'Ventilo-condenseur', 'vgrp': 4, 'unit': '', 'editavel': False},
        '239': {'name': 'ALARME GENERALE', 'vgrp': 4, 'unit': '', 'editavel': False},
        '240': {'name': 'Alarm Active', 'vgrp': 1, 'unit': '', 'editavel': False},
        '241': {'name': 'Remote OFF', 'vgrp': 1, 'unit': '', 'editavel': False},
        '242': {'name': 'OFF from RTC', 'vgrp': 1, 'unit': '', 'editavel': False},
        '244': {'name': 'Stand-by', 'vgrp': 1, 'unit': '', 'editavel': False},
        '245': {'name': 'Chiller', 'vgrp': 1, 'unit': '', 'editavel': False},
        '248': {'name': 'Unloading', 'vgrp': 1, 'unit': '', 'editavel': False},
        '249': {'name': 'Dynamic Set', 'vgrp': 1, 'unit': '', 'editavel': False},
        '250': {'name': 'Energy Saving', 'vgrp': 1, 'unit': '', 'editavel': False},
        '256': {'name': 'HP1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '257': {'name': 'HP2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '258': {'name': 'HP3', 'vgrp': 2, 'unit': '', 'editavel': False},
        '259': {'name': 'HP4', 'vgrp': 2, 'unit': '', 'editavel': False},
        '260': {'name': 'HPC1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '261': {'name': 'HPC2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '262': {'name': 'HPC3', 'vgrp': 2, 'unit': '', 'editavel': False},
        '263': {'name': 'HPC4', 'vgrp': 2, 'unit': '', 'editavel': False},
        '264': {'name': 'HPC5', 'vgrp': 2, 'unit': '', 'editavel': False},
        '265': {'name': 'HPC6', 'vgrp': 2, 'unit': '', 'editavel': False},
        '276': {'name': 'LP1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '277': {'name': 'LP2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '280': {'name': 'OPC1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '281': {'name': 'OPC2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '282': {'name': 'OPC3', 'vgrp': 2, 'unit': '', 'editavel': False},
        '283': {'name': 'OPC4', 'vgrp': 2, 'unit': '', 'editavel': False},
        '284': {'name': 'OPC5', 'vgrp': 2, 'unit': '', 'editavel': False},
        '285': {'name': 'OPC6', 'vgrp': 2, 'unit': '', 'editavel': False},
        '296': {'name': 'AHP1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '297': {'name': 'AHP2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '300': {'name': 'ALP1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '301': {'name': 'ALP2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '304': {'name': 'B1AC', 'vgrp': 2, 'unit': '', 'editavel': False},
        '305': {'name': 'B2AC', 'vgrp': 2, 'unit': '', 'editavel': False},
        '308': {'name': 'AB1AC', 'vgrp': 2, 'unit': '', 'editavel': False},
        '309': {'name': 'AB2AC', 'vgrp': 2, 'unit': '', 'editavel': False},
        '312': {'name': 'B1AH', 'vgrp': 2, 'unit': '', 'editavel': False},
        '313': {'name': 'B2AH', 'vgrp': 2, 'unit': '', 'editavel': False},
        '316': {'name': 'AB1AH', 'vgrp': 2, 'unit': '', 'editavel': False},
        '317': {'name': 'AB2AH', 'vgrp': 2, 'unit': '', 'editavel': False},
        '320': {'name': 'AB1A', 'vgrp': 2, 'unit': '', 'editavel': False},
        '321': {'name': 'AB2A', 'vgrp': 2, 'unit': '', 'editavel': False},
        '324': {'name': 'AEFL', 'vgrp': 2, 'unit': '', 'editavel': False},
        '325': {'name': 'ACFL', 'vgrp': 2, 'unit': '', 'editavel': False},
        '326': {'name': 'ATSF', 'vgrp': 2, 'unit': '', 'editavel': False},
        '327': {'name': 'C1TR', 'vgrp': 2, 'unit': '', 'editavel': False},
        '328': {'name': 'C2TR', 'vgrp': 2, 'unit': '', 'editavel': False},
        '329': {'name': 'C3TR', 'vgrp': 2, 'unit': '', 'editavel': False},
        '330': {'name': 'C4TR', 'vgrp': 2, 'unit': '', 'editavel': False},
        '331': {'name': 'C5TR', 'vgrp': 2, 'unit': '', 'editavel': False},
        '332': {'name': 'C6TR', 'vgrp': 2, 'unit': '', 'editavel': False},
        '343': {'name': 'C1DT', 'vgrp': 2, 'unit': '', 'editavel': False},
        '344': {'name': 'C2DT', 'vgrp': 2, 'unit': '', 'editavel': False},
        '345': {'name': 'C3DT', 'vgrp': 2, 'unit': '', 'editavel': False},
        '346': {'name': 'C4DT', 'vgrp': 2, 'unit': '', 'editavel': False},
        '347': {'name': 'C5DT', 'vgrp': 2, 'unit': '', 'editavel': False},
        '348': {'name': 'C6DT', 'vgrp': 2, 'unit': '', 'editavel': False},
        '359': {'name': 'AEHT', 'vgrp': 2, 'unit': '', 'editavel': False},
        '360': {'name': 'B1TF', 'vgrp': 2, 'unit': '', 'editavel': False},
        '361': {'name': 'B2TF', 'vgrp': 2, 'unit': '', 'editavel': False},
        '364': {'name': 'B1DF', 'vgrp': 2, 'unit': '', 'editavel': False},
        '365': {'name': 'B2DF', 'vgrp': 2, 'unit': '', 'editavel': False},
        '368': {'name': 'B1CUC', 'vgrp': 2, 'unit': '', 'editavel': False},
        '369': {'name': 'B2CUC', 'vgrp': 2, 'unit': '', 'editavel': False},
        '372': {'name': 'B1CUH', 'vgrp': 2, 'unit': '', 'editavel': False},
        '373': {'name': 'B2CUH', 'vgrp': 2, 'unit': '', 'editavel': False},
        '376': {'name': 'AEUN', 'vgrp': 2, 'unit': '', 'editavel': False},
        '377': {'name': 'ATE1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '378': {'name': 'ATE2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '379': {'name': 'ATC1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '380': {'name': 'ATC2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '381': {'name': 'ALC1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '382': {'name': 'ALC2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '383': {'name': 'C1MN', 'vgrp': 2, 'unit': '', 'editavel': False},
        '384': {'name': 'C2MN', 'vgrp': 2, 'unit': '', 'editavel': False},
        '385': {'name': 'C3MN', 'vgrp': 2, 'unit': '', 'editavel': False},
        '386': {'name': 'C4MN', 'vgrp': 2, 'unit': '', 'editavel': False},
        '387': {'name': 'C5MN', 'vgrp': 2, 'unit': '', 'editavel': False},
        '388': {'name': 'C6MN', 'vgrp': 2, 'unit': '', 'editavel': False},
        '399': {'name': 'AEP1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '400': {'name': 'AEP2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '401': {'name': 'ACP1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '402': {'name': 'ACP2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '403': {'name': 'AP1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '404': {'name': 'AP2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '405': {'name': 'AP3', 'vgrp': 2, 'unit': '', 'editavel': False},
        '406': {'name': 'AP4', 'vgrp': 2, 'unit': '', 'editavel': False},
        '407': {'name': 'AP5', 'vgrp': 2, 'unit': '', 'editavel': False},
        '408': {'name': 'AP6', 'vgrp': 2, 'unit': '', 'editavel': False},
        '409': {'name': 'AP7', 'vgrp': 2, 'unit': '', 'editavel': False},
        '410': {'name': 'AP8', 'vgrp': 2, 'unit': '', 'editavel': False},
        '411': {'name': 'AP9', 'vgrp': 2, 'unit': '', 'editavel': False},
        '412': {'name': 'AP10', 'vgrp': 2, 'unit': '', 'editavel': False},
        '413': {'name': 'AP1e1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '414': {'name': 'AP2e1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '415': {'name': 'AP3e1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '416': {'name': 'AP4e1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '417': {'name': 'AP5e1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '418': {'name': 'AP6e1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '419': {'name': 'AP7e1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '420': {'name': 'AP1e2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '421': {'name': 'AP2e2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '422': {'name': 'AP3e2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '423': {'name': 'AP4e2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '424': {'name': 'AP5e2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '425': {'name': 'AP6e2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '426': {'name': 'AP7e2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '427': {'name': 'AP1e3', 'vgrp': 2, 'unit': '', 'editavel': False},
        '428': {'name': 'AP2e3', 'vgrp': 2, 'unit': '', 'editavel': False},
        '429': {'name': 'AP3e3', 'vgrp': 2, 'unit': '', 'editavel': False},
        '430': {'name': 'AP4e3', 'vgrp': 2, 'unit': '', 'editavel': False},
        '434': {'name': 'AP1e4', 'vgrp': 2, 'unit': '', 'editavel': False},
        '435': {'name': 'AP2e4', 'vgrp': 2, 'unit': '', 'editavel': False},
        '436': {'name': 'AP3e4', 'vgrp': 2, 'unit': '', 'editavel': False},
        '437': {'name': 'AP4e4', 'vgrp': 2, 'unit': '', 'editavel': False},
        '441': {'name': 'C1TRP', 'vgrp': 2, 'unit': '', 'editavel': False},
        '442': {'name': 'C2TRP', 'vgrp': 2, 'unit': '', 'editavel': False},
        '443': {'name': 'C3TRP', 'vgrp': 2, 'unit': '', 'editavel': False},
        '444': {'name': 'C4TRP', 'vgrp': 2, 'unit': '', 'editavel': False},
        '445': {'name': 'C5TRP', 'vgrp': 2, 'unit': '', 'editavel': False},
        '446': {'name': 'C6TRP', 'vgrp': 2, 'unit': '', 'editavel': False},
        '457': {'name': 'B1PH', 'vgrp': 2, 'unit': '', 'editavel': False},
        '458': {'name': 'B1PHA', 'vgrp': 2, 'unit': '', 'editavel': False},
        '459': {'name': 'B2PH', 'vgrp': 2, 'unit': '', 'editavel': False},
        '460': {'name': 'B2PHA', 'vgrp': 2, 'unit': '', 'editavel': False},
        '465': {'name': 'B1PL', 'vgrp': 2, 'unit': '', 'editavel': False},
        '466': {'name': 'B1PLA', 'vgrp': 2, 'unit': '', 'editavel': False},
        '467': {'name': 'B2PL', 'vgrp': 2, 'unit': '', 'editavel': False},
        '468': {'name': 'B2PLA', 'vgrp': 2, 'unit': '', 'editavel': False},
        '473': {'name': 'B1RC', 'vgrp': 2, 'unit': '', 'editavel': False},
        '474': {'name': 'B2RC', 'vgrp': 2, 'unit': '', 'editavel': False},
        '477': {'name': 'AHFL', 'vgrp': 2, 'unit': '', 'editavel': False},
        '478': {'name': 'APFL', 'vgrp': 2, 'unit': '', 'editavel': False},
        '499': {'name': 'ANDY', 'vgrp': 2, 'unit': '', 'editavel': False},
        '500': {'name': 'ACF1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '501': {'name': 'ACF2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '502': {'name': 'ACF3', 'vgrp': 2, 'unit': '', 'editavel': False},
        '503': {'name': 'ACF4', 'vgrp': 2, 'unit': '', 'editavel': False},
        '504': {'name': 'ACF5', 'vgrp': 2, 'unit': '', 'editavel': False},
        '505': {'name': 'ACF6', 'vgrp': 2, 'unit': '', 'editavel': False},
        '506': {'name': 'ACF7', 'vgrp': 2, 'unit': '', 'editavel': False},
        '507': {'name': 'ACF8', 'vgrp': 2, 'unit': '', 'editavel': False},
        '508': {'name': 'ACF9', 'vgrp': 2, 'unit': '', 'editavel': False},
        '509': {'name': 'ACF10', 'vgrp': 2, 'unit': '', 'editavel': False},
        '510': {'name': 'ACF11', 'vgrp': 2, 'unit': '', 'editavel': False},
        '511': {'name': 'ACF12', 'vgrp': 2, 'unit': '', 'editavel': False},
        '512': {'name': 'ACF13', 'vgrp': 2, 'unit': '', 'editavel': False},
        '513': {'name': 'ACF14', 'vgrp': 2, 'unit': '', 'editavel': False},
        '514': {'name': 'ACF15', 'vgrp': 2, 'unit': '', 'editavel': False},
        '515': {'name': 'ACF16', 'vgrp': 2, 'unit': '', 'editavel': False},
        '516': {'name': 'APS', 'vgrp': 2, 'unit': '', 'editavel': False},
        '517': {'name': 'NOIPEX1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '518': {'name': 'NOIPEX2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '521': {'name': 'AFR', 'vgrp': 2, 'unit': '', 'editavel': False},
        '522': {'name': 'ACF17', 'vgrp': 2, 'unit': '', 'editavel': False},
        '523': {'name': 'ACF18', 'vgrp': 2, 'unit': '', 'editavel': False},
        '524': {'name': 'AFFC', 'vgrp': 2, 'unit': '', 'editavel': False},
        '525': {'name': 'ATRB', 'vgrp': 2, 'unit': '', 'editavel': False},
        '526': {'name': 'ALCB', 'vgrp': 2, 'unit': '', 'editavel': False},
        '527': {'name': 'ACF19', 'vgrp': 2, 'unit': '', 'editavel': False},
        '570': {'name': 'ACF20', 'vgrp': 2, 'unit': '', 'editavel': False},
        '571': {'name': 'AHINV', 'vgrp': 2, 'unit': '', 'editavel': False},
        '572': {'name': 'ATEC1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '573': {'name': 'ATEC2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '574': {'name': 'ACFHINV', 'vgrp': 2, 'unit': '', 'editavel': False},
        '575': {'name': 'ACFCOP', 'vgrp': 2, 'unit': '', 'editavel': False},
        '576': {'name': 'APBHI', 'vgrp': 2, 'unit': '', 'editavel': False},
        '577': {'name': 'ACFET', 'vgrp': 2, 'unit': '', 'editavel': False},
        '578': {'name': 'ACFHW', 'vgrp': 2, 'unit': '', 'editavel': False},
        '579': {'name': 'ATS1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '580': {'name': 'ATS2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '581': {'name': 'ACFCONEU', 'vgrp': 2, 'unit': '', 'editavel': False},
        '582': {'name': 'ACFHI5', 'vgrp': 2, 'unit': '', 'editavel': False},
        '583': {'name': 'ACFHI6', 'vgrp': 2, 'unit': '', 'editavel': False},
        '594': {'name': 'Set Point AUX n.4', 'vgrp': 0, 'unit': '°C', 'editavel': True},
        '100000': {'name': 'No-Link', 'vgrp': 2, 'unit': '', 'editavel': False},
        '100002': {'name': 'Status', 'vgrp': 0, 'unit': '', 'editavel': False}
    },
    '37': {
        '1': {'name': 'entree d eau glacee primaire', 'vgrp': 0, 'unit': '°C', 'editavel': False},
        '2': {'name': 'sortie d eau glacee primaire', 'vgrp': 0, 'unit': '°C', 'editavel': False},
        '3': {'name': 'Delta P index2', 'vgrp': 0, 'unit': 'bar', 'editavel': False},
        '4': {'name': 'condensation circuit HP1', 'vgrp': 0, 'unit': 'bar', 'editavel': False},
        '5': {'name': 'condensation circuit HP2', 'vgrp': 0, 'unit': 'bar', 'editavel': False},
        '7': {'name': 'sortie du ballon pour vanne index', 'vgrp': 0, 'unit': '°C', 'editavel': False},
        '8': {'name': 'depart eau glacee index', 'vgrp': 0, 'unit': '°C', 'editavel': False},
        '10': {'name': 'Delta P index1', 'vgrp': 0, 'unit': 'bar', 'editavel': False},
        '55': {'name': 'vitesse condenseur', 'vgrp': 0, 'unit': '%', 'editavel': False},
        '56': {'name': 'vanne 3 voies index', 'vgrp': 0, 'unit': '%', 'editavel': True},
        '57': {'name': 'vitesse pompe index', 'vgrp': 0, 'unit': '%', 'editavel': True},
        '73': {'name': 'consigne actuelle reglee', 'vgrp': 0, 'unit': '°C', 'editavel': False},
        '106': {'name': 'consigne eau glacee', 'vgrp': 0, 'unit': '°C', 'editavel': True},
        '113': {'name': 'InverterPump SetPoint User Side', 'vgrp': 0, 'unit': 'bar', 'editavel': True},
        '169': {'name': 'etat pressostat Haute pression 1', 'vgrp': 3, 'unit': '', 'editavel': False},
        '170': {'name': 'etat pressostat basse pression 1', 'vgrp': 3, 'unit': '', 'editavel': False},
        '171': {'name': 'etat pressostat Haute pression 2', 'vgrp': 3, 'unit': '', 'editavel': False},
        '172': {'name': 'etat pressostat basse pression 2', 'vgrp': 3, 'unit': '', 'editavel': False},
        '173': {'name': 'etat relais thermique comp 1/a', 'vgrp': 3, 'unit': '', 'editavel': False},
        '174': {'name': 'etat relais thermique comp 2/a', 'vgrp': 3, 'unit': '', 'editavel': False},
        '175': {'name': 'etat relais thermique comp 3/a', 'vgrp': 3, 'unit': '', 'editavel': False},
        '176': {'name': 'etat relais thermique comp 1/b', 'vgrp': 3, 'unit': '', 'editavel': False},
        '177': {'name': 'etat relais thermique comp 2/b', 'vgrp': 3, 'unit': '', 'editavel': False},
        '178': {'name': 'etat relais thermique comp 3/b', 'vgrp': 3, 'unit': '', 'editavel': False},
        '179': {'name': 'etat relais debit d eau primaire', 'vgrp': 3, 'unit': '', 'editavel': False},
        '180': {'name': 'etat pompe primaire 1', 'vgrp': 3, 'unit': '', 'editavel': False},
        '181': {'name': 'etat pompe primaire 2', 'vgrp': 3, 'unit': '', 'editavel': False},
        '182': {'name': 'etat relais thermique ventilocond', 'vgrp': 3, 'unit': '', 'editavel': False},
        '183': {'name': 'etat des pompes secondaires', 'vgrp': 3, 'unit': '', 'editavel': False},
        '188': {'name': 'etat controleur de phases', 'vgrp': 3, 'unit': '', 'editavel': False},
        '225': {'name': 'Compresseur 1 circuit A', 'vgrp': 4, 'unit': '', 'editavel': False},
        '226': {'name': 'Compresseur 2 circuit A', 'vgrp': 4, 'unit': '', 'editavel': False},
        '227': {'name': 'Compresseur 3 circuit A', 'vgrp': 4, 'unit': '', 'editavel': False},
        '228': {'name': 'Compresseur 1 circuit B', 'vgrp': 4, 'unit': '', 'editavel': False},
        '229': {'name': 'Compresseur 2 circuit B', 'vgrp': 4, 'unit': '', 'editavel': False},
        '230': {'name': 'Compresseur 3 circuit B', 'vgrp': 4, 'unit': '', 'editavel': False},
        '231': {'name': 'Pompe primaire 1', 'vgrp': 4, 'unit': '', 'editavel': False},
        '232': {'name': 'Pompe primaire 2', 'vgrp': 4, 'unit': '', 'editavel': False},
        '234': {'name': 'Pompe index', 'vgrp': 4, 'unit': '', 'editavel': False},
        '237': {'name': 'Ventilo-condenseur', 'vgrp': 4, 'unit': '', 'editavel': False},
        '239': {'name': 'ALARME GENERALE', 'vgrp': 4, 'unit': '', 'editavel': False},
        '240': {'name': 'Alarm Active', 'vgrp': 1, 'unit': '', 'editavel': False},
        '241': {'name': 'Remote OFF', 'vgrp': 1, 'unit': '', 'editavel': False},
        '242': {'name': 'OFF from RTC', 'vgrp': 1, 'unit': '', 'editavel': False},
        '244': {'name': 'Stand-by', 'vgrp': 1, 'unit': '', 'editavel': False},
        '245': {'name': 'Chiller', 'vgrp': 1, 'unit': '', 'editavel': False},
        '248': {'name': 'Unloading', 'vgrp': 1, 'unit': '', 'editavel': False},
        '249': {'name': 'Dynamic Set', 'vgrp': 1, 'unit': '', 'editavel': False},
        '250': {'name': 'Energy Saving', 'vgrp': 1, 'unit': '', 'editavel': False},
        '256': {'name': 'HP1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '257': {'name': 'HP2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '258': {'name': 'HP3', 'vgrp': 2, 'unit': '', 'editavel': False},
        '259': {'name': 'HP4', 'vgrp': 2, 'unit': '', 'editavel': False},
        '260': {'name': 'HPC1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '261': {'name': 'HPC2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '262': {'name': 'HPC3', 'vgrp': 2, 'unit': '', 'editavel': False},
        '263': {'name': 'HPC4', 'vgrp': 2, 'unit': '', 'editavel': False},
        '264': {'name': 'HPC5', 'vgrp': 2, 'unit': '', 'editavel': False},
        '265': {'name': 'HPC6', 'vgrp': 2, 'unit': '', 'editavel': False},
        '276': {'name': 'LP1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '277': {'name': 'LP2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '280': {'name': 'OPC1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '281': {'name': 'OPC2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '282': {'name': 'OPC3', 'vgrp': 2, 'unit': '', 'editavel': False},
        '283': {'name': 'OPC4', 'vgrp': 2, 'unit': '', 'editavel': False},
        '284': {'name': 'OPC5', 'vgrp': 2, 'unit': '', 'editavel': False},
        '285': {'name': 'OPC6', 'vgrp': 2, 'unit': '', 'editavel': False},
        '296': {'name': 'AHP1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '297': {'name': 'AHP2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '300': {'name': 'ALP1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '301': {'name': 'ALP2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '304': {'name': 'B1AC', 'vgrp': 2, 'unit': '', 'editavel': False},
        '305': {'name': 'B2AC', 'vgrp': 2, 'unit': '', 'editavel': False},
        '308': {'name': 'AB1AC', 'vgrp': 2, 'unit': '', 'editavel': False},
        '309': {'name': 'AB2AC', 'vgrp': 2, 'unit': '', 'editavel': False},
        '312': {'name': 'B1AH', 'vgrp': 2, 'unit': '', 'editavel': False},
        '313': {'name': 'B2AH', 'vgrp': 2, 'unit': '', 'editavel': False},
        '316': {'name': 'AB1AH', 'vgrp': 2, 'unit': '', 'editavel': False},
        '317': {'name': 'AB2AH', 'vgrp': 2, 'unit': '', 'editavel': False},
        '320': {'name': 'AB1A', 'vgrp': 2, 'unit': '', 'editavel': False},
        '321': {'name': 'AB2A', 'vgrp': 2, 'unit': '', 'editavel': False},
        '324': {'name': 'AEFL', 'vgrp': 2, 'unit': '', 'editavel': False},
        '325': {'name': 'ACFL', 'vgrp': 2, 'unit': '', 'editavel': False},
        '326': {'name': 'ATSF', 'vgrp': 2, 'unit': '', 'editavel': False},
        '327': {'name': 'C1TR', 'vgrp': 2, 'unit': '', 'editavel': False},
        '328': {'name': 'C2TR', 'vgrp': 2, 'unit': '', 'editavel': False},
        '329': {'name': 'C3TR', 'vgrp': 2, 'unit': '', 'editavel': False},
        '330': {'name': 'C4TR', 'vgrp': 2, 'unit': '', 'editavel': False},
        '331': {'name': 'C5TR', 'vgrp': 2, 'unit': '', 'editavel': False},
        '332': {'name': 'C6TR', 'vgrp': 2, 'unit': '', 'editavel': False},
        '343': {'name': 'C1DT', 'vgrp': 2, 'unit': '', 'editavel': False},
        '344': {'name': 'C2DT', 'vgrp': 2, 'unit': '', 'editavel': False},
        '345': {'name': 'C3DT', 'vgrp': 2, 'unit': '', 'editavel': False},
        '346': {'name': 'C4DT', 'vgrp': 2, 'unit': '', 'editavel': False},
        '347': {'name': 'C5DT', 'vgrp': 2, 'unit': '', 'editavel': False},
        '348': {'name': 'C6DT', 'vgrp': 2, 'unit': '', 'editavel': False},
        '359': {'name': 'AEHT', 'vgrp': 2, 'unit': '', 'editavel': False},
        '360': {'name': 'B1TF', 'vgrp': 2, 'unit': '', 'editavel': False},
        '361': {'name': 'B2TF', 'vgrp': 2, 'unit': '', 'editavel': False},
        '364': {'name': 'B1DF', 'vgrp': 2, 'unit': '', 'editavel': False},
        '365': {'name': 'B2DF', 'vgrp': 2, 'unit': '', 'editavel': False},
        '368': {'name': 'B1CUC', 'vgrp': 2, 'unit': '', 'editavel': False},
        '369': {'name': 'B2CUC', 'vgrp': 2, 'unit': '', 'editavel': False},
        '372': {'name': 'B1CUH', 'vgrp': 2, 'unit': '', 'editavel': False},
        '373': {'name': 'B2CUH', 'vgrp': 2, 'unit': '', 'editavel': False},
        '376': {'name': 'AEUN', 'vgrp': 2, 'unit': '', 'editavel': False},
        '377': {'name': 'ATE1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '378': {'name': 'ATE2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '379': {'name': 'ATC1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '380': {'name': 'ATC2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '381': {'name': 'ALC1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '382': {'name': 'ALC2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '383': {'name': 'C1MN', 'vgrp': 2, 'unit': '', 'editavel': False},
        '384': {'name': 'C2MN', 'vgrp': 2, 'unit': '', 'editavel': False},
        '385': {'name': 'C3MN', 'vgrp': 2, 'unit': '', 'editavel': False},
        '386': {'name': 'C4MN', 'vgrp': 2, 'unit': '', 'editavel': False},
        '387': {'name': 'C5MN', 'vgrp': 2, 'unit': '', 'editavel': False},
        '388': {'name': 'C6MN', 'vgrp': 2, 'unit': '', 'editavel': False},
        '399': {'name': 'AEP1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '400': {'name': 'AEP2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '401': {'name': 'ACP1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '402': {'name': 'ACP2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '403': {'name': 'AP1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '404': {'name': 'AP2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '405': {'name': 'AP3', 'vgrp': 2, 'unit': '', 'editavel': False},
        '406': {'name': 'AP4', 'vgrp': 2, 'unit': '', 'editavel': False},
        '407': {'name': 'AP5', 'vgrp': 2, 'unit': '', 'editavel': False},
        '408': {'name': 'AP6', 'vgrp': 2, 'unit': '', 'editavel': False},
        '409': {'name': 'AP7', 'vgrp': 2, 'unit': '', 'editavel': False},
        '410': {'name': 'AP8', 'vgrp': 2, 'unit': '', 'editavel': False},
        '411': {'name': 'AP9', 'vgrp': 2, 'unit': '', 'editavel': False},
        '412': {'name': 'AP10', 'vgrp': 2, 'unit': '', 'editavel': False},
        '413': {'name': 'AP1e1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '414': {'name': 'AP2e1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '415': {'name': 'AP3e1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '416': {'name': 'AP4e1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '417': {'name': 'AP5e1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '418': {'name': 'AP6e1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '419': {'name': 'AP7e1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '420': {'name': 'AP1e2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '421': {'name': 'AP2e2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '422': {'name': 'AP3e2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '423': {'name': 'AP4e2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '424': {'name': 'AP5e2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '425': {'name': 'AP6e2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '426': {'name': 'AP7e2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '427': {'name': 'AP1e3', 'vgrp': 2, 'unit': '', 'editavel': False},
        '428': {'name': 'AP2e3', 'vgrp': 2, 'unit': '', 'editavel': False},
        '429': {'name': 'AP3e3', 'vgrp': 2, 'unit': '', 'editavel': False},
        '430': {'name': 'AP4e3', 'vgrp': 2, 'unit': '', 'editavel': False},
        '434': {'name': 'AP1e4', 'vgrp': 2, 'unit': '', 'editavel': False},
        '435': {'name': 'AP2e4', 'vgrp': 2, 'unit': '', 'editavel': False},
        '436': {'name': 'AP3e4', 'vgrp': 2, 'unit': '', 'editavel': False},
        '437': {'name': 'AP4e4', 'vgrp': 2, 'unit': '', 'editavel': False},
        '441': {'name': 'C1TRP', 'vgrp': 2, 'unit': '', 'editavel': False},
        '442': {'name': 'C2TRP', 'vgrp': 2, 'unit': '', 'editavel': False},
        '443': {'name': 'C3TRP', 'vgrp': 2, 'unit': '', 'editavel': False},
        '444': {'name': 'C4TRP', 'vgrp': 2, 'unit': '', 'editavel': False},
        '445': {'name': 'C5TRP', 'vgrp': 2, 'unit': '', 'editavel': False},
        '446': {'name': 'C6TRP', 'vgrp': 2, 'unit': '', 'editavel': False},
        '457': {'name': 'B1PH', 'vgrp': 2, 'unit': '', 'editavel': False},
        '458': {'name': 'B1PHA', 'vgrp': 2, 'unit': '', 'editavel': False},
        '459': {'name': 'B2PH', 'vgrp': 2, 'unit': '', 'editavel': False},
        '460': {'name': 'B2PHA', 'vgrp': 2, 'unit': '', 'editavel': False},
        '465': {'name': 'B1PL', 'vgrp': 2, 'unit': '', 'editavel': False},
        '466': {'name': 'B1PLA', 'vgrp': 2, 'unit': '', 'editavel': False},
        '467': {'name': 'B2PL', 'vgrp': 2, 'unit': '', 'editavel': False},
        '468': {'name': 'B2PLA', 'vgrp': 2, 'unit': '', 'editavel': False},
        '473': {'name': 'B1RC', 'vgrp': 2, 'unit': '', 'editavel': False},
        '474': {'name': 'B2RC', 'vgrp': 2, 'unit': '', 'editavel': False},
        '477': {'name': 'AHFL', 'vgrp': 2, 'unit': '', 'editavel': False},
        '478': {'name': 'APFL', 'vgrp': 2, 'unit': '', 'editavel': False},
        '499': {'name': 'ANDY', 'vgrp': 2, 'unit': '', 'editavel': False},
        '500': {'name': 'ACF1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '501': {'name': 'ACF2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '502': {'name': 'ACF3', 'vgrp': 2, 'unit': '', 'editavel': False},
        '503': {'name': 'ACF4', 'vgrp': 2, 'unit': '', 'editavel': False},
        '504': {'name': 'ACF5', 'vgrp': 2, 'unit': '', 'editavel': False},
        '505': {'name': 'ACF6', 'vgrp': 2, 'unit': '', 'editavel': False},
        '506': {'name': 'ACF7', 'vgrp': 2, 'unit': '', 'editavel': False},
        '507': {'name': 'ACF8', 'vgrp': 2, 'unit': '', 'editavel': False},
        '508': {'name': 'ACF9', 'vgrp': 2, 'unit': '', 'editavel': False},
        '509': {'name': 'ACF10', 'vgrp': 2, 'unit': '', 'editavel': False},
        '510': {'name': 'ACF11', 'vgrp': 2, 'unit': '', 'editavel': False},
        '511': {'name': 'ACF12', 'vgrp': 2, 'unit': '', 'editavel': False},
        '512': {'name': 'ACF13', 'vgrp': 2, 'unit': '', 'editavel': False},
        '513': {'name': 'ACF14', 'vgrp': 2, 'unit': '', 'editavel': False},
        '514': {'name': 'ACF15', 'vgrp': 2, 'unit': '', 'editavel': False},
        '515': {'name': 'ACF16', 'vgrp': 2, 'unit': '', 'editavel': False},
        '516': {'name': 'APS', 'vgrp': 2, 'unit': '', 'editavel': False},
        '517': {'name': 'NOIPEX1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '518': {'name': 'NOIPEX2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '521': {'name': 'AFR', 'vgrp': 2, 'unit': '', 'editavel': False},
        '522': {'name': 'ACF17', 'vgrp': 2, 'unit': '', 'editavel': False},
        '523': {'name': 'ACF18', 'vgrp': 2, 'unit': '', 'editavel': False},
        '524': {'name': 'AFFC', 'vgrp': 2, 'unit': '', 'editavel': False},
        '525': {'name': 'ATRB', 'vgrp': 2, 'unit': '', 'editavel': False},
        '526': {'name': 'ALCB', 'vgrp': 2, 'unit': '', 'editavel': False},
        '527': {'name': 'ACF19', 'vgrp': 2, 'unit': '', 'editavel': False},
        '570': {'name': 'ACF20', 'vgrp': 2, 'unit': '', 'editavel': False},
        '571': {'name': 'AHINV', 'vgrp': 2, 'unit': '', 'editavel': False},
        '572': {'name': 'ATEC1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '573': {'name': 'ATEC2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '574': {'name': 'ACFHINV', 'vgrp': 2, 'unit': '', 'editavel': False},
        '575': {'name': 'ACFCOP', 'vgrp': 2, 'unit': '', 'editavel': False},
        '576': {'name': 'APBHI', 'vgrp': 2, 'unit': '', 'editavel': False},
        '577': {'name': 'ACFET', 'vgrp': 2, 'unit': '', 'editavel': False},
        '578': {'name': 'ACFHW', 'vgrp': 2, 'unit': '', 'editavel': False},
        '579': {'name': 'ATS1', 'vgrp': 2, 'unit': '', 'editavel': False},
        '580': {'name': 'ATS2', 'vgrp': 2, 'unit': '', 'editavel': False},
        '581': {'name': 'ACFCONEU', 'vgrp': 2, 'unit': '', 'editavel': False},
        '582': {'name': 'ACFHI5', 'vgrp': 2, 'unit': '', 'editavel': False},
        '583': {'name': 'ACFHI6', 'vgrp': 2, 'unit': '', 'editavel': False},
        '594': {'name': 'Set Point AUX n.4', 'vgrp': 0, 'unit': '°C', 'editavel': True},
        '100000': {'name': 'No-Link', 'vgrp': 2, 'unit': '', 'editavel': False},
        '100002': {'name': 'Status', 'vgrp': 0, 'unit': '', 'editavel': False}
    }
}

# ============================================================
# ESTADO GLOBAL
# ============================================================
state = {
    'sses': None,
    'logged_in': False,
    'user_id': None,
    'device_36': None,
    'device_37': None,
    'last_update': None,
    'point_names': POINT_NAMES,
    'command_names': {}
}

# ============================================================
# FUNÇÃO PARA IDENTIFICAR ALARMES
# ============================================================
def is_alarm_name(name):
    """Verifica se o nome do ponto é um alarme conhecido"""
    if not name:
        return False
    
    alarm_names = [
        'HP1', 'HP2', 'HP3', 'HP4', 'HPC1', 'HPC2', 'HPC3', 'HPC4', 'HPC5', 'HPC6',
        'LP1', 'LP2', 'OPC1', 'OPC2', 'OPC3', 'OPC4', 'OPC5', 'OPC6',
        'AHP1', 'AHP2', 'ALP1', 'ALP2',
        'B1AC', 'B2AC', 'AB1AC', 'AB2AC', 'B1AH', 'B2AH', 'AB1AH', 'AB2AH',
        'AB1A', 'AB2A', 'AEFL', 'ACFL', 'ATSF',
        'C1TR', 'C2TR', 'C3TR', 'C4TR', 'C5TR', 'C6TR',
        'C1DT', 'C2DT', 'C3DT', 'C4DT', 'C5DT', 'C6DT', 'AEHT',
        'B1TF', 'B2TF', 'B1DF', 'B2DF', 'B1CUC', 'B2CUC', 'B1CUH', 'B2CUH',
        'AEUN', 'ATE1', 'ATE2', 'ATC1', 'ATC2', 'ALC1', 'ALC2',
        'C1MN', 'C2MN', 'C3MN', 'C4MN', 'C5MN', 'C6MN',
        'AEP1', 'AEP2', 'ACP1', 'ACP2',
        'AP1', 'AP2', 'AP3', 'AP4', 'AP5', 'AP6', 'AP7', 'AP8', 'AP9', 'AP10',
        'C1TRP', 'C2TRP', 'C3TRP', 'C4TRP', 'C5TRP', 'C6TRP',
        'B1PH', 'B1PHA', 'B2PH', 'B2PHA', 'B1PL', 'B1PLA', 'B2PL', 'B2PLA',
        'B1RC', 'B2RC', 'AHFL', 'APFL', 'ANDY',
        'APS', 'NOIPEX1', 'NOIPEX2', 'AFR', 'AFFC', 'ATRB', 'ALCB',
        'AHINV', 'ATEC1', 'ATEC2', 'ACFHINV', 'ACFCOP', 'APBHI', 'ACFET', 'ACFHW',
        'ATS1', 'ATS2', 'ACFCONEU', 'ACFHI5', 'ACFHI6',
        'ACF1', 'ACF2', 'ACF3', 'ACF4', 'ACF5', 'ACF6', 'ACF7', 'ACF8', 'ACF9', 'ACF10',
        'ACF11', 'ACF12', 'ACF13', 'ACF14', 'ACF15', 'ACF16', 'ACF17', 'ACF18', 'ACF19', 'ACF20',
        'AP1e1', 'AP2e1', 'AP3e1', 'AP4e1', 'AP5e1', 'AP6e1', 'AP7e1',
        'AP1e2', 'AP2e2', 'AP3e2', 'AP4e2', 'AP5e2', 'AP6e2', 'AP7e2',
        'AP1e3', 'AP2e3', 'AP3e3', 'AP4e3', 'AP1e4', 'AP2e4', 'AP3e4', 'AP4e4',
        'No-Link', 'ALARME GENERALE', 'Alarm Active'
    ]
    
    name_upper = name.upper()
    for alarm in alarm_names:
        if alarm.upper() in name_upper:
            return True
    return False

# ============================================================
# ROTA PARA SERVER O HTML
# ============================================================
@app.route('/')
def serve_index():
    return send_from_directory('.', 'chillers.html')

@app.route('/chillers.html')
def serve_chillers():
    return send_from_directory('.', 'chillers.html')

# ============================================================
# ENDPOINTS DE AUTENTICAÇÃO
# ============================================================

@app.route('/api/auth/login', methods=['POST'])
def auth_login():
    """Endpoint para autenticar no sistema"""
    data = request.get_json()
    
    if not data:
        return jsonify({
            'success': False,
            'error': 'Dados inválidos'
        }), 400
    
    password = data.get('password', '')
    
    if password == CONFIG['security_password']:
        session['authenticated'] = True
        session['auth_time'] = datetime.now().isoformat()
        return jsonify({
            'success': True,
            'message': 'Autenticado com sucesso',
            'timestamp': time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime())
        })
    else:
        return jsonify({
            'success': False,
            'error': 'Senha incorreta'
        }), 401

@app.route('/api/auth/logout', methods=['POST'])
def auth_logout():
    """Endpoint para desautenticar"""
    session.pop('authenticated', None)
    session.pop('auth_time', None)
    return jsonify({
        'success': True,
        'message': 'Desautenticado com sucesso'
    })

@app.route('/api/auth/status', methods=['GET'])
def auth_status():
    """Verifica o status da autenticação"""
    is_authenticated = session.get('authenticated', False)
    auth_time = session.get('auth_time')
    
    # Verificar se a sessão expirou
    if is_authenticated and auth_time:
        try:
            auth_dt = datetime.fromisoformat(auth_time)
            if datetime.now() - auth_dt > timedelta(seconds=CONFIG['session_timeout']):
                session.pop('authenticated', None)
                session.pop('auth_time', None)
                is_authenticated = False
        except:
            pass
    
    return jsonify({
        'success': True,
        'authenticated': is_authenticated,
        'timestamp': time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime())
    })

@app.route('/api/auth/change-password', methods=['POST'])
def auth_change_password():
    """Altera a senha de segurança"""
    data = request.get_json()
    
    if not data:
        return jsonify({
            'success': False,
            'error': 'Dados inválidos'
        }), 400
    
    current_password = data.get('current_password', '')
    new_password = data.get('new_password', '')
    
    if current_password != CONFIG['security_password']:
        return jsonify({
            'success': False,
            'error': 'Senha atual incorreta'
        }), 401
    
    if len(new_password) < 4:
        return jsonify({
            'success': False,
            'error': 'Nova senha deve ter pelo menos 4 caracteres'
        }), 400
    
    CONFIG['security_password'] = new_password
    
    # Salvar em arquivo de configuração (opcional)
    try:
        with open('security_config.json', 'w') as f:
            json.dump({'password': new_password}, f)
    except:
        pass
    
    return jsonify({
        'success': True,
        'message': 'Senha alterada com sucesso'
    })

# ============================================================
# 1. FUNCAO DE LOGIN
# ============================================================
def login():
    print('[LOGIN] Tentando login como: ' + CONFIG['username'])
    
    url = f'{CONFIG["xweb_url"]}/cgi-bin/login.lua'
    
    payload = {
        'action': 'login',
        'auth_mode': 'standard',
        'username': CONFIG['username'],
        'password': CONFIG['password'],
        'initparam': '-1'
    }
    
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
        'X-Requested-With': 'XMLHttpRequest'
    }
    
    try:
        response = requests.post(url, data=payload, headers=headers, timeout=15)
        data = response.json()
        
        if data.get('status') == 'ok' and data.get('user', {}).get('sses'):
            sses = data['user']['sses']
            state['sses'] = sses
            state['logged_in'] = True
            state['user_id'] = data['user'].get('id')
            
            print('[LOGIN] OK! SSES: ' + sses)
            print('[LOGIN] User ID: ' + str(state['user_id']))
            return True
        else:
            print('[LOGIN] Falhou: ' + data.get('error', 'Erro desconhecido'))
            return False
            
    except Exception as e:
        print('[LOGIN] Erro: ' + str(e))
        return False


# ============================================================
# 2. FUNCAO PARA CARREGAR SETUP
# ============================================================
def load_setup():
    if not state['sses']:
        print('[SETUP] Sem SSES para carregar setup')
        return False
    
    url = f'{CONFIG["xweb_url"]}/cgi-bin/getsetup.lua'
    
    payload = {
        'language': CONFIG['language'],
        'SSES': state['sses']
    }
    
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
        'X-Requested-With': 'XMLHttpRequest'
    }
    
    try:
        response = requests.post(url, data=payload, headers=headers, timeout=30)
        data = response.json()
        
        if data and data.get('devices'):
            print('[SETUP] Carregado! ' + str(len(data['devices'])) + ' dispositivos')
            return True
        else:
            print('[SETUP] Invalido')
            return False
            
    except Exception as e:
        print('[SETUP] Erro: ' + str(e))
        return False


# ============================================================
# 3. FUNÇÃO PARA CARREGAR NOMES DOS COMANDOS
# ============================================================
def load_command_names():
    """Carrega os nomes dos comandos de todos os dispositivos"""
    if not state['sses']:
        print('[COMMANDS] Sem SSES para carregar comandos')
        return False
    
    url = f'{CONFIG["xweb_url"]}/cgi-bin/getsetup.lua'
    
    payload = {
        'language': CONFIG['language'],
        'SSES': state['sses']
    }
    
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
        'X-Requested-With': 'XMLHttpRequest'
    }
    
    try:
        response = requests.post(url, data=payload, headers=headers, timeout=30)
        data = response.json()
        
        if data and data.get('devices'):
            command_names = {}
            
            for device_id, device_data in data['devices'].items():
                if device_data.get('cmds'):
                    command_names[device_id] = {}
                    for cmd_id, cmd_data in device_data['cmds'].items():
                        nome = cmd_data.get('cds') or cmd_data.get('cn') or cmd_data.get('ucds') or cmd_data.get('descricao') or f'CMD{cmd_id}'
                        
                        try:
                            nome = urllib.parse.unquote(nome)
                        except:
                            pass
                        
                        nome = nome.strip()
                        
                        command_names[device_id][cmd_id] = {
                            'id': int(cmd_id),
                            'nome': nome,
                            'ativo': cmd_data.get('act') == 1,
                            'cds': cmd_data.get('cds', ''),
                            'cn': cmd_data.get('cn', ''),
                            'ucds': cmd_data.get('ucds', ''),
                            'descricao': cmd_data.get('descricao', '')
                        }
            
            state['command_names'] = command_names
            print('[COMMANDS] Nomes dos comandos carregados para ' + str(len(command_names)) + ' dispositivos')
            
            for device_id, cmds in command_names.items():
                ativos = [c for c in cmds.values() if c['ativo']]
                print(f'[COMMANDS] Device {device_id}: {len(ativos)} comandos ativos')
                for cmd in ativos[:5]:
                    print(f'  🎮 {cmd["id"]}: {cmd["nome"]}')
                if len(ativos) > 5:
                    print(f'  ... e mais {len(ativos) - 5} comandos')
            
            return True
        else:
            print('[COMMANDS] Setup invalido')
            return False
            
    except Exception as e:
        print(f'[COMMANDS] Erro: {str(e)}')
        return False


# ============================================================
# 4. FUNCAO PARA COLETAR DADOS DO STREAM
# ============================================================
def collect_stream_data():
    if not state['sses']:
        print('[STREAM] Sem SSES para coletar stream')
        return False
    
    url = f'{CONFIG["xweb_url"]}/cgi-bin/stream.lua'
    
    payload = {
        'SSES': state['sses'],
        'complete': 1
    }
    
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
        'X-Requested-With': 'XMLHttpRequest'
    }
    
    try:
        response = requests.post(url, data=payload, headers=headers, timeout=20)
        data = response.json()
        
        if data.get('status') == 'ok' and data.get('stream', {}).get('data'):
            stream_data = data['stream']['data']
            
            for device_id in CONFIG['device_ids']:
                device_id_str = str(device_id)
                if device_id_str in stream_data:
                    process_device_data(device_id, stream_data[device_id_str])
            
            state['last_update'] = time.strftime('%Y-%m-%d %H:%M:%S')
            print('[STREAM] Coletado e processado!')
            return True
        else:
            print('[STREAM] Sem dados')
            return False
            
    except Exception as e:
        print('[STREAM] Erro: ' + str(e))
        return False


# ============================================================
# 5. FUNCAO PARA PROCESSAR DADOS DE UM DISPOSITIVO
# ============================================================
def process_device_data(device_id, raw_data):
    device_id_str = str(device_id)
    point_names = state['point_names'].get(device_id_str, {})
    
    analogicos = []
    digitais = []
    
    temperatura = raw_data.get('2', None)
    setpoint = None
    
    for point_id, value in raw_data.items():
        if value is None:
            continue
        
        info = point_names.get(point_id, {
            'name': f'P{point_id}',
            'vgrp': 0,
            'unit': '',
            'editavel': False
        })
        
        vgrp = info.get('vgrp', 0)
        if vgrp is None:
            vgrp = 0
        vgrp = int(vgrp)
        
        ponto = {
            'id': int(point_id),
            'nome': info.get('name', f'P{point_id}'),
            'valor': value,
            'unidade': info.get('unit', ''),
            'vgrp': vgrp,
            'editavel': info.get('editavel', False)
        }
        
        if vgrp == 0:
            analogicos.append(ponto)
            nome_lower = str(ponto['nome']).lower()
            if any(word in nome_lower for word in ['set', 'consigne', 'setpoint', 'regl']):
                if setpoint is None:
                    setpoint = value
        else:
            digitais.append(ponto)
        
        if point_id in ['101', '106'] and setpoint is None:
            setpoint = value
    
    analogicos.sort(key=lambda x: str(x['nome']))
    digitais.sort(key=lambda x: str(x['nome']))
    
    device_key = f'device_{device_id}'
    state[device_key] = {
        'id': device_id,
        'timestamp': time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime()),
        'temperatura': temperatura,
        'setpoint': setpoint,
        'total_pontos': len(raw_data),
        'analogicos': analogicos,
        'digitais': digitais,
        'ativo': True,
        'nome': f'LDA sn {device_id}',
        'endereco': f'RS1-00{device_id-35}'
    }


# ============================================================
# 6. FUNCAO PARA ESCREVER VALOR (COM VERIFICAÇÃO DE AUTENTICAÇÃO)
# ============================================================
def write_value(device_id, point_id, value):
    if not state['sses']:
        return {'success': False, 'error': 'Sessao invalida'}
    
    # Verificar se o usuário está autenticado
    if not session.get('authenticated'):
        return {
            'success': False, 
            'error': 'Autenticação necessária para escrever valores',
            'code': 'AUTH_REQUIRED'
        }
    
    device_id_str = str(device_id)
    point_id_str = str(point_id)
    info = state['point_names'].get(device_id_str, {}).get(point_id_str, {})
    
    if not info.get('editavel', False):
        return {
            'success': False, 
            'error': f'O ponto {point_id} ({info.get("name", "desconhecido")}) nao e editavel'
        }
    
    url = f'{CONFIG["xweb_url"]}/cgi-bin/devcmd.cgi'
    
    payload = {
        'D_0': device_id,
        'W_0': point_id,
        'V_0': value,
        'hp': 1,
        'sses': state['sses'],
        'plgid': CONFIG['plgid']
    }
    
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    }
    
    try:
        response = requests.post(url, data=payload, headers=headers, timeout=10)
        
        if response.text.startswith('{'):
            data = response.json()
        else:
            json_match = re.search(r'\{.*\}', response.text)
            if json_match:
                data = json.loads(json_match.group())
            else:
                data = {'devcmd': [{'er': 0}]}
        
        if data.get('devcmd') and len(data['devcmd']) > 0:
            result = data['devcmd'][0]
            if result.get('er') == 0:
                print(f'[WRITE] OK! Device: {device_id}, Point: {point_id}, Value: {value}')
                return {
                    'success': True,
                    'message': f'Valor alterado com sucesso',
                    'device_id': device_id,
                    'point_id': point_id,
                    'value': value,
                    'name': info.get('name', f'P{point_id}')
                }
            else:
                return {
                    'success': False,
                    'error': f'Erro {result.get("er")}'
                }
        else:
            return {
                'success': True,
                'message': 'Valor alterado com sucesso',
                'device_id': device_id,
                'point_id': point_id,
                'value': value
            }
            
    except Exception as e:
        print(f'[WRITE] Erro: {str(e)}')
        return {
            'success': False,
            'error': str(e)
        }


# ============================================================
# 7. FUNÇÃO PARA BUSCAR COMANDOS
# ============================================================
def get_device_commands(device_id):
    """Busca os comandos disponíveis para um dispositivo"""
    device_id_str = str(device_id)
    
    if 'command_names' in state and device_id_str in state['command_names']:
        comandos = list(state['command_names'][device_id_str].values())
        comandos = [c for c in comandos if c['ativo']]
        comandos.sort(key=lambda x: x['nome'])
        return {
            'success': True,
            'device_id': device_id,
            'comandos': comandos
        }
    
    if not state['sses']:
        return {'success': False, 'error': 'Sessao invalida'}
    
    url = f'{CONFIG["xweb_url"]}/cgi-bin/getsetup.lua'
    
    payload = {
        'language': CONFIG['language'],
        'SSES': state['sses']
    }
    
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
        'X-Requested-With': 'XMLHttpRequest'
    }
    
    try:
        response = requests.post(url, data=payload, headers=headers, timeout=30)
        data = response.json()
        
        if data and data.get('devices'):
            if device_id_str in data['devices']:
                device = data['devices'][device_id_str]
                comandos = []
                
                if device.get('cmds'):
                    for cmd_id, cmd_data in device['cmds'].items():
                        nome = cmd_data.get('cds') or cmd_data.get('cn') or cmd_data.get('ucds') or cmd_data.get('descricao') or f'CMD{cmd_id}'
                        
                        try:
                            nome = urllib.parse.unquote(nome)
                        except:
                            pass
                        
                        nome = nome.strip()
                        
                        if cmd_data.get('act') == 1:
                            comandos.append({
                                'id': int(cmd_id),
                                'nome': nome,
                                'ativo': True,
                                'cds': cmd_data.get('cds', ''),
                                'cn': cmd_data.get('cn', '')
                            })
                    
                    comandos.sort(key=lambda x: x['nome'])
                    
                    return {
                        'success': True,
                        'device_id': device_id,
                        'comandos': comandos
                    }
            
            return {
                'success': False,
                'error': f'Dispositivo {device_id} nao encontrado'
            }
        else:
            return {
                'success': False,
                'error': 'Setup invalido'
            }
            
    except Exception as e:
        print(f'[COMMANDS] Erro: {str(e)}')
        return {'success': False, 'error': str(e)}


# ============================================================
# 8. FUNCAO PARA EXECUTAR COMANDO (COM VERIFICAÇÃO DE AUTENTICAÇÃO)
# ============================================================
def execute_command(device_id, command_id):
    if not state['sses']:
        return {'success': False, 'error': 'Sessao invalida'}
    
    # Verificar se o usuário está autenticado
    if not session.get('authenticated'):
        return {
            'success': False, 
            'error': 'Autenticação necessária para executar comandos',
            'code': 'AUTH_REQUIRED'
        }
    
    url = f'{CONFIG["xweb_url"]}/cgi-bin/getsetup.lua'
    
    payload = {
        'language': CONFIG['language'],
        'SSES': state['sses']
    }
    
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
        'X-Requested-With': 'XMLHttpRequest'
    }
    
    try:
        response = requests.post(url, data=payload, headers=headers, timeout=30)
        data = response.json()
        
        device_id_str = str(device_id)
        command_id_str = str(command_id)
        
        if not data or not data.get('devices') or device_id_str not in data['devices']:
            return {'success': False, 'error': f'Dispositivo {device_id} nao encontrado'}
        
        device = data['devices'][device_id_str]
        
        if not device.get('cmds') or command_id_str not in device['cmds']:
            return {'success': False, 'error': f'Comando {command_id} nao encontrado'}
        
        cmd_data = device['cmds'][command_id_str]
        nome = cmd_data.get('cds') or cmd_data.get('cn') or cmd_data.get('ucds') or f'CMD{command_id}'
        
        url_cmd = f'{CONFIG["xweb_url"]}/cgi-bin/devcmd.cgi'
        
        payload_cmd = {
            'D_0': device_id,
            'C_0': command_id,
            'hp': 1,
            'sses': state['sses'],
            'plgid': '9f7d06c3c28efaae40f1b2d7b5df5ef2'
        }
        
        headers_cmd = {
            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
        }
        
        response_cmd = requests.post(url_cmd, data=payload_cmd, headers=headers_cmd, timeout=10)
        
        try:
            result = response_cmd.json()
        except:
            json_match = re.search(r'\{.*\}', response_cmd.text)
            if json_match:
                result = json.loads(json_match.group())
            else:
                result = {'devcmd': [{'er': 0}]}
        
        if result.get('devcmd') and len(result['devcmd']) > 0:
            cmd_result = result['devcmd'][0]
            if cmd_result.get('er') == 0:
                return {
                    'success': True,
                    'message': f'Comando "{nome}" executado com sucesso',
                    'device_id': device_id,
                    'command_id': command_id,
                    'command_name': nome
                }
            else:
                return {
                    'success': False,
                    'error': f'Erro {cmd_result.get("er")} ao executar comando'
                }
        else:
            return {
                'success': True,
                'message': f'Comando "{nome}" executado',
                'device_id': device_id,
                'command_id': command_id,
                'command_name': nome
            }
            
    except Exception as e:
        print(f'[EXECUTE] Erro: {str(e)}')
        return {'success': False, 'error': str(e)}


# ============================================================
# 9. ENDPOINTS DA API
# ============================================================

@app.route('/api/device/<int:device_id>', methods=['GET'])
@require_auth_optional
def get_device(device_id, is_authenticated=False):
    """Obtém dados de um dispositivo específico"""
    if device_id == 36:
        data = state.get('device_36')
    elif device_id == 37:
        data = state.get('device_37')
    else:
        return jsonify({
            'success': False,
            'error': 'Dispositivo nao encontrado'
        }), 404
    
    if data:
        response = {
            'success': True,
            'device_id': device_id,
            'data': data,
            'timestamp': time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime()),
            'authenticated': is_authenticated
        }
        return jsonify(response)
    else:
        return jsonify({
            'success': False,
            'error': f'Device {device_id} not available'
        }), 404


@app.route('/api/devices', methods=['GET'])
@require_auth_optional
def get_all_devices(is_authenticated=False):
    """Obtém dados de todos os dispositivos"""
    devices = {}
    
    for device_id in CONFIG['device_ids']:
        key = f'device_{device_id}'
        if state.get(key):
            devices[device_id] = state[key]
    
    return jsonify({
        'success': True,
        'timestamp': time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime()),
        'devices': devices,
        'refresh_interval': CONFIG['refresh_interval'],
        'authenticated': is_authenticated
    })


@app.route('/api/status', methods=['GET'])
@require_auth_optional
def get_status(is_authenticated=False):
    """Obtém o status do sistema"""
    return jsonify({
        'success': True,
        'timestamp': time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime()),
        'session': {
            'logged_in': state['logged_in'],
            'user_id': state['user_id']
        },
        'devices': {
            '36': {
                'available': state.get('device_36') is not None,
                'temperatura': state.get('device_36', {}).get('temperatura'),
                'setpoint': state.get('device_36', {}).get('setpoint')
            },
            '37': {
                'available': state.get('device_37') is not None,
                'temperatura': state.get('device_37', {}).get('temperatura'),
                'setpoint': state.get('device_37', {}).get('setpoint')
            }
        },
        'last_update': state.get('last_update'),
        'refresh_interval': CONFIG['refresh_interval'],
        'authenticated': is_authenticated
    })


@app.route('/api/refresh', methods=['POST'])
@require_auth_optional
def refresh(is_authenticated=False):
    """Força a atualização dos dados"""
    if collect_stream_data():
        return jsonify({
            'success': True,
            'message': 'Dados atualizados com sucesso',
            'timestamp': time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime()),
            'authenticated': is_authenticated
        })
    else:
        return jsonify({
            'success': False,
            'error': 'Falha ao atualizar dados'
        }), 500


@app.route('/api/write', methods=['POST'])
@require_auth
def api_write():
    """Altera o valor de um ponto (requer autenticação)"""
    data = request.get_json()
    
    if not data:
        return jsonify({
            'success': False,
            'error': 'Body JSON e obrigatorio'
        }), 400
    
    device_id = data.get('device_id')
    point_id = data.get('point_id')
    value = data.get('value')
    
    if device_id is None or point_id is None or value is None:
        return jsonify({
            'success': False,
            'error': 'Campos obrigatorios: device_id, point_id, value'
        }), 400
    
    result = write_value(device_id, point_id, value)
    
    if result['success']:
        collect_stream_data()
        return jsonify(result)
    else:
        return jsonify(result), 500 if result.get('code') != 'AUTH_REQUIRED' else 401


@app.route('/api/setpoint', methods=['POST'])
@require_auth
def api_setpoint():
    """Altera o setpoint de um dispositivo (requer autenticação)"""
    data = request.get_json()
    
    if not data:
        return jsonify({
            'success': False,
            'error': 'Body JSON e obrigatorio'
        }), 400
    
    device_id = data.get('device_id')
    value = data.get('value')
    
    if device_id is None or value is None:
        return jsonify({
            'success': False,
            'error': 'Campos obrigatorios: device_id, value'
        }), 400
    
    setpoint_ids = [106]
    point_id = None
    
    for pid in setpoint_ids:
        info = state['point_names'].get(str(device_id), {}).get(str(pid), {})
        if info.get('editavel', False):
            point_id = pid
            break
    
    if point_id is None:
        return jsonify({
            'success': False,
            'error': f'Setpoint editavel nao encontrado para o dispositivo {device_id}'
        }), 404
    
    result = write_value(device_id, point_id, value)
    
    if result['success']:
        collect_stream_data()
        return jsonify(result)
    else:
        return jsonify(result), 500 if result.get('code') != 'AUTH_REQUIRED' else 401


# ============================================================
# 10. ENDPOINTS PARA COMANDOS
# ============================================================

@app.route('/api/commands/<int:device_id>', methods=['GET'])
@require_auth_optional
def api_get_commands(device_id, is_authenticated=False):
    """Obtém a lista de comandos de um dispositivo"""
    result = get_device_commands(device_id)
    
    if result['success']:
        result['authenticated'] = is_authenticated
        return jsonify(result)
    else:
        return jsonify(result), 404


@app.route('/api/commands/all', methods=['GET'])
@require_auth_optional
def api_get_all_commands(is_authenticated=False):
    """Obtém todos os comandos de todos os dispositivos"""
    result = {}
    
    for device_id in CONFIG['device_ids']:
        cmd_result = get_device_commands(device_id)
        if cmd_result['success']:
            result[device_id] = cmd_result['comandos']
    
    return jsonify({
        'success': True,
        'devices': result,
        'timestamp': time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime()),
        'authenticated': is_authenticated
    })


@app.route('/api/command/execute', methods=['POST'])
@require_auth
def api_execute_command():
    """Executa um comando (requer autenticação)"""
    data = request.get_json()
    
    if not data:
        return jsonify({
            'success': False,
            'error': 'Body JSON e obrigatorio'
        }), 400
    
    device_id = data.get('device_id')
    command_id = data.get('command_id')
    
    if device_id is None or command_id is None:
        return jsonify({
            'success': False,
            'error': 'Campos obrigatorios: device_id, command_id'
        }), 400
    
    result = execute_command(device_id, command_id)
    
    if result['success']:
        collect_stream_data()
        return jsonify(result)
    else:
        return jsonify(result), 500 if result.get('code') != 'AUTH_REQUIRED' else 401


@app.route('/api/reconnect', methods=['POST'])
def api_reconnect():
    """Força a reconexão com o XWEB"""
    try:
        state['sses'] = None
        state['logged_in'] = False
        
        if login():
            load_setup()
            load_command_names()
            collect_stream_data()
            return jsonify({
                'success': True,
                'message': 'Reconectado com sucesso',
                'timestamp': time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime())
            })
        else:
            return jsonify({
                'success': False,
                'error': 'Falha ao reconectar - credenciais inválidas'
            }), 401
    except Exception as e:
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500


# ============================================================
# 11. LOOP DE ATUALIZACAO
# ============================================================
def update_loop():
    print('[LOOP] Iniciando loop de atualizacao (intervalo: ' + str(CONFIG['refresh_interval']) + 's)')
    
    while True:
        try:
            if not state['logged_in'] or not state['sses']:
                print('[LOOP] Sessao expirada, tentando login...')
                if not login():
                    print('[LOOP] Aguardando 10 segundos...')
                    time.sleep(10)
                    continue
                load_setup()
                load_command_names()
            
            collect_stream_data()
            time.sleep(CONFIG['refresh_interval'])
            
        except Exception as e:
            print(f'[LOOP] Erro: {str(e)}')
            time.sleep(10)


# ============================================================
# 12. ENDPOINTS PARA GRÁFICOS
# ============================================================

@app.route('/api/chart/points', methods=['GET'])
@require_auth_optional
def api_chart_points(is_authenticated=False):
    """Retorna a lista de pontos disponíveis para gráficos"""
    
    device_id = request.args.get('device_id', type=int)
    include_digital = request.args.get('include_digital', default='false').lower() == 'true'
    
    if not device_id:
        return jsonify({
            'success': False,
            'error': 'device_id é obrigatório'
        }), 400
    
    device_id_str = str(device_id)
    point_names = state['point_names'].get(device_id_str, {})
    
    points = []
    
    # Mapeamento de tipos VGRP
    vgrp_map = {
        0: 'Analógico',
        1: 'Status',
        2: 'Alarme',
        3: 'Alarme',
        4: 'Alarme'
    }
    
    for point_id, info in point_names.items():
        vgrp = info.get('vgrp', 0)
        
        # Incluir analógicos (vgrp=0) sempre
        if vgrp == 0:
            points.append({
                'id': int(point_id),
                'name': info.get('name', f'P{point_id}'),
                'unit': info.get('unit', ''),
                'vgrp': vgrp,
                'type': 'Analógico',
                'editavel': info.get('editavel', False)
            })
        # Incluir digitais/alarmes se solicitado
        elif include_digital and vgrp in [1, 2, 3, 4]:
            # Verificar se é um alarme (pelos nomes conhecidos)
            is_alarm = is_alarm_name(info.get('name', ''))
            point_type = 'Alarme' if is_alarm else 'Digital'
            
            points.append({
                'id': int(point_id),
                'name': info.get('name', f'P{point_id}'),
                'unit': info.get('unit', ''),
                'vgrp': vgrp,
                'type': point_type,
                'editavel': info.get('editavel', False)
            })
    
    # Ordenar: primeiro analógicos, depois alarmes, depois outros
    points.sort(key=lambda x: (0 if x['type'] == 'Analógico' else 1, x['name']))
    
    return jsonify({
        'success': True,
        'device_id': device_id,
        'points': points,
        'authenticated': is_authenticated,
        'timestamp': time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime())
    })


@app.route('/api/chart/data', methods=['POST'])
@require_auth
def api_chart_data():
    """Busca dados históricos para gráficos usando o formato correto do XWEB"""
    
    data = request.get_json()
    
    if not data:
        return jsonify({
            'success': False,
            'error': 'Body JSON é obrigatório'
        }), 400
    
    device_id = data.get('device_id')
    point_ids = data.get('point_ids', [])
    start_time = data.get('start_time')
    end_time = data.get('end_time')
    
    # Validar
    if not device_id:
        return jsonify({
            'success': False,
            'error': 'device_id é obrigatório'
        }), 400
    
    if not point_ids or len(point_ids) == 0:
        return jsonify({
            'success': False,
            'error': 'Selecione pelo menos um ponto'
        }), 400
    
    if not start_time or not end_time:
        return jsonify({
            'success': False,
            'error': 'start_time e end_time são obrigatórios'
        }), 400
    
    if not state['sses']:
        return jsonify({
            'success': False,
            'error': 'Sessão XWEB inválida'
        }), 401
    
    # Converter datas para timestamp
    try:
        # Tentar diferentes formatos
        if 'T' in start_time:
            # Formato ISO: 2026-07-12T10:00:00
            start_dt = datetime.fromisoformat(start_time.replace('Z', '+00:00'))
            end_dt = datetime.fromisoformat(end_time.replace('Z', '+00:00'))
        else:
            # Formato: 2026-07-12 10:00:00
            start_dt = datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S')
            end_dt = datetime.strptime(end_time, '%Y-%m-%d %H:%M:%S')
        
        start_ts = int(start_dt.timestamp())
        end_ts = int(end_dt.timestamp())
    except Exception as e:
        return jsonify({
            'success': False,
            'error': f'Formato de data inválido: {str(e)}'
        }), 400
    
    # Verificar período máximo (31 dias)
    if (end_ts - start_ts) > 31 * 24 * 3600:
        return jsonify({
            'success': False,
            'error': 'Período máximo de 31 dias'
        }), 400
    
    # Adicionar pontos obrigatórios (No-Link e Status)
    required_points = [100000, 100002]
    all_points = list(set(point_ids + required_points))
    
    # Construir request no formato correto do XWEB
    request_data = {
        str(device_id): all_points
    }
    
    # Construir payload
    payload = {
        'SSES': state['sses'],
        'source': 'main',
        'startTS': start_ts,
        'stopTS': end_ts,
        'sampling': 0,
        'request': json.dumps(request_data),
        'dbg': ''
    }
    
    url = f'{CONFIG["xweb_url"]}/cgi-bin/datachart.cgi'
    
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
        'X-Requested-With': 'XMLHttpRequest'
    }
    
    try:
        print(f'[CHART] Enviando requisição para XWEB...')
        print(f'[CHART] Device: {device_id}, Points: {point_ids}')
        print(f'[CHART] Período: {start_time} → {end_time}')
        
        response = requests.post(url, data=payload, headers=headers, timeout=120)
        
        if response.status_code != 200:
            return jsonify({
                'success': False,
                'error': f'HTTP {response.status_code}'
            }), response.status_code
        
        # Parse da resposta
        try:
            result = response.json()
        except Exception as e:
            print(f'[CHART] Erro ao parsear JSON: {str(e)}')
            return jsonify({
                'success': False,
                'error': 'Resposta inválida do XWEB'
            }), 500
        
        # Processar dados
        device_id_str = str(device_id)
        if device_id_str in result:
            records = result[device_id_str]
            
            # Verificar se há dados
            if not records or len(records) == 0:
                return jsonify({
                    'success': False,
                    'error': 'Nenhum dado encontrado para o período selecionado'
                }), 404
            
            # Limitar a 2000 pontos para performance
            original_count = len(records)
            if original_count > 2000:
                step = max(1, original_count // 2000)
                records = records[::step]
                sampled = True
            else:
                sampled = False
            
            # Obter nomes dos pontos
            device_id_str = str(device_id)
            point_names = state['point_names'].get(device_id_str, {})
            point_info = {}
            
            for pid in point_ids:
                pid_str = str(pid)
                info = point_names.get(pid_str, {})
                point_info[pid_str] = {
                    'name': info.get('name', f'P{pid}'),
                    'unit': info.get('unit', ''),
                    'vgrp': info.get('vgrp', 0),
                    'type': 'Alarme' if is_alarm_name(info.get('name', '')) else ('Analógico' if info.get('vgrp', 0) == 0 else 'Digital'),
                    'editavel': info.get('editavel', False)
                }
            
            # Formatar resposta
            formatted_data = {
                'success': True,
                'device_id': device_id,
                'point_ids': point_ids,
                'point_info': point_info,
                'start_time': start_time,
                'end_time': end_time,
                'start_timestamp': start_ts,
                'end_timestamp': end_ts,
                'total_records': len(records),
                'original_count': original_count,
                'sampled': sampled,
                'records': records,
                'timestamp': time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime())
            }
            
            print(f'[CHART] ✅ Dados obtidos: {len(records)} registros')
            return jsonify(formatted_data)
        else:
            return jsonify({
                'success': False,
                'error': f'Dispositivo {device_id} não encontrado nos dados'
            }), 404
            
    except requests.Timeout:
        print(f'[CHART] ❌ Timeout')
        return jsonify({
            'success': False,
            'error': 'Timeout ao buscar dados (mais de 120 segundos)'
        }), 504
    except Exception as e:
        print(f'[CHART] ❌ Erro: {str(e)}')
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500


@app.route('/api/chart/presets', methods=['GET'])
@require_auth_optional
def api_chart_presets(is_authenticated=False):
    """Retorna presets de períodos para gráficos"""
    
    presets = [
        {'id': 'last_hour', 'label': 'Última 1 hora', 'hours': 1},
        {'id': 'last_6h', 'label': 'Últimas 6 horas', 'hours': 6},
        {'id': 'last_12h', 'label': 'Últimas 12 horas', 'hours': 12},
        {'id': 'last_24h', 'label': 'Últimas 24 horas', 'hours': 24},
        {'id': 'last_3d', 'label': 'Últimos 3 dias', 'hours': 72},
        {'id': 'last_7d', 'label': 'Últimos 7 dias', 'hours': 168},
        {'id': 'last_15d', 'label': 'Últimos 15 dias', 'hours': 360},
        {'id': 'last_30d', 'label': 'Últimos 30 dias', 'hours': 720}
    ]
    
    return jsonify({
        'success': True,
        'presets': presets,
        'authenticated': is_authenticated,
        'timestamp': time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime())
    })


# ============================================================
# 13. INICIALIZACAO
# ============================================================
def init():
    print('=' * 60)
    print('INICIANDO XWEB DATA COLLECTOR (Python/Flask)')
    print('=' * 60)
    print('URL: ' + CONFIG['xweb_url'])
    print('Dispositivos: ' + str(CONFIG['device_ids']))
    print('Refresh interval: ' + str(CONFIG['refresh_interval']) + 's')
    print('Senha de segurança: ' + ('*' * len(CONFIG['security_password'])))
    print('=' * 60)
    
    # Tentar carregar senha do arquivo
    try:
        with open('security_config.json', 'r') as f:
            config = json.load(f)
            if config.get('password'):
                CONFIG['security_password'] = config['password']
                print('[INIT] Senha carregada do arquivo')
    except:
        pass
    
    if not login():
        print('[INIT] Login inicial falhou!')
        return
    
    print('[INIT] Aguardando 5 segundos para setup...')
    time.sleep(5)
    
    if not load_setup():
        print('[INIT] Falha ao carregar setup!')
        return
    
    if not load_command_names():
        print('[INIT] Falha ao carregar nomes dos comandos!')
    
    collect_stream_data()
    
    print('[INIT] Sistema inicializado com sucesso!')
    print('')
    print('ENDPOINTS DISPONIVEIS:')
    print('  GET  /chillers.html            - Frontend')
    print('  GET  /api/device/36            - Dados do dispositivo 36')
    print('  GET  /api/device/37            - Dados do dispositivo 37')
    print('  GET  /api/devices              - Todos os dispositivos')
    print('  GET  /api/status               - Status do sistema')
    print('  POST /api/refresh              - Forcar atualizacao')
    print('  POST /api/write                - Alterar ponto (REQUER AUTH)')
    print('  POST /api/setpoint             - Alterar setpoint (REQUER AUTH)')
    print('  GET  /api/commands/36          - Comandos do dispositivo 36')
    print('  GET  /api/commands/37          - Comandos do dispositivo 37')
    print('  GET  /api/commands/all         - Todos os comandos')
    print('  POST /api/command/execute      - Executar comando (REQUER AUTH)')
    print('  POST /api/auth/login           - Autenticar')
    print('  POST /api/auth/logout          - Desautenticar')
    print('  GET  /api/auth/status          - Status da autenticação')
    print('  POST /api/auth/change-password - Alterar senha')
    print('  POST /api/reconnect            - Reconectar ao XWEB')
    print('')
    print('📊 ENDPOINTS PARA GRÁFICOS:')
    print('  GET  /api/chart/points         - Pontos disponíveis para gráficos (inclui alarmes)')
    print('  POST /api/chart/data           - Buscar dados históricos')
    print('  GET  /api/chart/presets        - Presets de períodos')


# ============================================================
# 14. EXECUCAO
# ============================================================
if __name__ == '__main__':
    import threading
    
    init()
    
    thread = threading.Thread(target=update_loop, daemon=True)
    thread.start()
    
    print('')
    print('🔐 SENHA DE SEGURANÇA: ' + CONFIG['security_password'])
    print('   (Altere nas configurações do frontend)')
    print('')
    print('Iniciando servidor Flask...')
    print('Acesse: http://localhost:5003/chillers.html')
    app.run(host='0.0.0.0', port=5003, debug=False)