/**
 * @copyright Utilitech AS 2023-2026
 * License: Fair Source
 * 
 */

#include "CloudConnector.h"
#include "FirmwareVersion.h"
#include "crc.h"
#include "Uptime.h"
#include "hexutils.h"
#if defined(ESP32)
#include <ESPRandom.h>
#endif

#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
#include "esp32/rom/rtc.h"
#elif CONFIG_IDF_TARGET_ESP32S2
#include "esp32s2/rom/rtc.h"
#elif CONFIG_IDF_TARGET_ESP32C3
#include "esp32c3/rom/rtc.h"
#elif CONFIG_IDF_TARGET_ESP32S3
#include "esp32s3/rom/rtc.h"
#endif

#if defined(AMS_REMOTE_DEBUG)
CloudConnector::CloudConnector(RemoteDebug* debugger) {
#else
CloudConnector::CloudConnector(Stream* debugger) {
#endif
    this->debugger = debugger;

    http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
    http.setReuse(false);
    http.setTimeout(60000);
    http.setUserAgent("ams2mqtt/" + String(FirmwareVersion::VersionString));
    http.useHTTP10(true);

    uint8_t mac[6];
    uint8_t apmac[6];

    #if defined(ESP8266)
    wifi_get_macaddr(STATION_IF, mac);
    wifi_get_macaddr(SOFTAP_IF, apmac);
	#elif defined(ESP32)
    esp_wifi_get_mac((wifi_interface_t)ESP_IF_WIFI_STA, mac);
    esp_wifi_get_mac((wifi_interface_t)ESP_IF_WIFI_AP, apmac);
	#endif
    sprintf_P(this->mac, PSTR("%02X:%02X:%02X:%02X:%02X:%02X"), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    sprintf_P(this->apmac, PSTR("%02X:%02X:%02X:%02X:%02X:%02X"), apmac[0], apmac[1], apmac[2], apmac[3], apmac[4], apmac[5]);
}

bool CloudConnector::setup(CloudConfig& config, MeterConfig& meter, SystemConfig& system, NtpConfig& ntp, HwTools* hw, ResetDataContainer* rdc, PriceService* ps) {
    bool ret = false;
    #if defined(ESP32)
    if(!ESPRandom::isValidV4Uuid(config.clientId)) {
        ESPRandom::uuid4(config.clientId);
        ret = true;
    }
    uuid = ESPRandom::uuidToString(config.clientId);
    #endif

    this->config = config;
    this->hw = hw;
    this->rdc = rdc;
    this->ps = ps;

    this->boardType = system.boardType;
    strcpy(this->timezone, ntp.timezone);

	this->maxPwr = 0;
	this->distributionSystem = meter.distributionSystem;
	this->mainFuse = meter.mainFuse;
	this->productionCapacity = meter.productionCapacity;

    this->initialized = false;

    return ret;
}

void CloudConnector::setMqttHandler(AmsMqttHandler* mqttHandler) {
    this->mqttHandler = mqttHandler;
}

bool CloudConnector::init() {
    if(config.enabled) {
        //if(config.port == 0) 
        config.port = 7443;
        //if(strlen(config.hostname) == 0) 
        strcpy_P(config.hostname, PSTR("cloud.amsleser.no"));

        snprintf_P(clearBuffer, CC_BUF_SIZE, PSTR("http://%s/hub/cloud/public.key"), config.hostname);
        #if defined(AMS_REMOTE_DEBUG)
        if (debugger->isActive(RemoteDebug::INFO))
        #endif
        debugger->printf_P(PSTR("(CloudConnector) Downloading public key from %s\n"), clearBuffer);
        #if defined(ESP8266)
        WiFiClient client;
        client.setTimeout(5000);
        if(http.begin(client, clearBuffer)) {
        #elif defined(ESP32)
        if(http.begin(clearBuffer)) {
        #endif
            int status = http.GET();

            #if defined(ESP32)
                esp_task_wdt_reset();
            #elif defined(ESP8266)
                ESP.wdtFeed();
            #endif

            if(status == HTTP_CODE_OK) {
                String pub = http.getString();
                http.end();

                memset(clearBuffer, 0, CC_BUF_SIZE);
                snprintf(clearBuffer, CC_BUF_SIZE, pub.c_str());

                mbedtls_pk_context pk;
                mbedtls_pk_init(&pk);

                int error_code = 0;
                if((error_code =  mbedtls_pk_parse_public_key(&pk, (unsigned char*) clearBuffer, strlen((const char*) clearBuffer)+1)) == 0){
                    rsa = mbedtls_pk_rsa(pk);
                    mbedtls_ctr_drbg_init(&ctr_drbg);
                    mbedtls_entropy_init(&entropy);

                    int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func,
                                                &entropy, (const unsigned char *) pers,
                                                strlen(pers));
                    if(ret != 0) {
                        #if defined(AMS_REMOTE_DEBUG)
                        if (debugger->isActive(RemoteDebug::ERROR))
                        #endif
                        debugger->printf_P(PSTR("mbedtls_ctr_drbg_seed return code: %d\n"), ret);
                    }
                    return ret == 0;
                } else {
                    #if defined(AMS_REMOTE_DEBUG)
                    if (debugger->isActive(RemoteDebug::ERROR))
                    #endif
                    debugger->printf("RSA public key read error: ");
                    mbedtls_strerror(error_code, clearBuffer, CC_BUF_SIZE);
                    #if defined(AMS_REMOTE_DEBUG)
                    if (debugger->isActive(RemoteDebug::ERROR))
                    #endif
                    debugger->printf("%s\n", clearBuffer);
                }
            } else {
                #if defined(AMS_REMOTE_DEBUG)
                if (debugger->isActive(RemoteDebug::ERROR))
                #endif
                debugger->printf_P(PSTR("(CloudConnector) Communication error, returned status: %d\n"), status);
                #if defined(AMS_REMOTE_DEBUG)
                if (debugger->isActive(RemoteDebug::ERROR))
                #endif
                debugger->printf(http.errorToString(status).c_str());
                debugger->println();
                #if defined(AMS_REMOTE_DEBUG)
                if (debugger->isActive(RemoteDebug::DEBUG))
                #endif
                debugger->printf(http.getString().c_str());

                http.end();
            }
        }
    }
    return false;
}

void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
    unsigned long now = millis();
    if(now-lastUpdate < ((unsigned long)config.interval)*1000) {
        return;
    };
    bool sendFirst = lastUpdate == 0;
    lastUpdate = now;
    if(config.enabled) {
        #if defined(AMS_REMOTE_DEBUG)
        if (debugger->isActive(RemoteDebug::VERBOSE))
        #endif
        debugger->printf_P(PSTR("(CloudConnector) Enabled\n"));
    } else {
        #if defined(AMS_REMOTE_DEBUG)
        if (debugger->isActive(RemoteDebug::VERBOSE))
        #endif
        debugger->printf_P(PSTR("(CloudConnector) Not enabled\n"));
        return;
    }
    if(!ESPRandom::isValidV4Uuid(config.clientId)) {
        #if defined(AMS_REMOTE_DEBUG)
        if (debugger->isActive(RemoteDebug::WARNING))
        #endif
        debugger->printf_P(PSTR("(CloudConnector) Client ID is not valid\n"));
        return;
    }
    if(data.getListType() < 2) {
        #if defined(AMS_REMOTE_DEBUG)
        if (debugger->isActive(RemoteDebug::VERBOSE))
        #endif
        debugger->printf_P(PSTR("(CloudConnector) List type not enough data\n"));
        return;
    }

    if(!initialized && !init()) {
        #if defined(AMS_REMOTE_DEBUG)
        if (debugger->isActive(RemoteDebug::WARNING))
        #endif
        debugger->printf_P(PSTR("Unable to initialize cloud connector\n"));
        return;
    }
    initialized = true;

    memset(clearBuffer, 0, CC_BUF_SIZE);

    int pos = 0;

    pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("{\"id\":\"%s\""), uuid.c_str());
    if(!seed.isEmpty()) {
        pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"seed\":\"%s\""), seed.c_str());
    }

    bool sendData = true;
    if(sendFirst) {
        seed.clear();
        if(mainFuse > 0 && distributionSystem > 0) {
            int voltage = distributionSystem == 2 ? 400 : 230;
            if(data.isThreePhase()) {
                maxPwr = mainFuse * sqrt(3) * voltage;
            } else if(data.isTwoPhase()) {
                maxPwr = mainFuse * voltage;
            } else {
                maxPwr = mainFuse * 230;
            }
        }

        IPAddress localIp;
        IPAddress subnet;
        IPAddress gateway;
        IPAddress dns1;
        IPAddress dns2;

        if(ch == NULL) {
            localIp = WiFi.localIP();
            subnet = IPAddress(255,255,255,0);
            gateway = WiFi.subnetMask();
            dns1 = WiFi.dnsIP(0);
            dns2 = WiFi.dnsIP(1);
        } else {
            localIp = ch->getIP();
            subnet = ch->getSubnetMask();
            gateway = ch->getGateway();
            dns1 = ch->getDns(0);
            dns2 = ch->getDns(1);
        }

        pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_INIT,
            mac, 
            apmac, 
            FirmwareVersion::VersionString,
            boardType,
            rtc_get_reset_reason(0),
            rdc == NULL ? 0 : rdc->last_cause,
            timezone,
            data.getMeterType(),
            meterManufacturer(data.getMeterType()).c_str(),
            data.getMeterModel().c_str(),
            data.getMeterId().c_str(),
            distributionSystemStr(distributionSystem).c_str(),
            mainFuse,
            maxPwr,
            productionCapacity,
            localIp.toString().c_str(),
            subnet.toString().c_str(),
            gateway.toString().c_str(),
            dns1.toString().c_str(),
            dns2.toString().c_str()
        );
    } else if(lastPriceConfig == 0) {
        pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"price\":{\"area\":\"%s\",\"currency\":\"%s\",\"modifiers\":["), priceConfig.area, priceConfig.currency);
        if(ps != NULL) {
            std::vector<PriceConfig> pc = ps->getPriceConfig();
            if(pc.size() > 0) {
                for(uint8_t i = 0; i < pc.size(); i++) {
                    PriceConfig& p = pc.at(i);

                    String days;
                    for(uint8_t d = 0; d < 7; d++) {
                        if((p.days >> d) & 0x1 == 0x1) {
                            days += String(d, 10) + ",";
                        }
                    }
                    days = days.substring(0, days.length()-1);

                    String hours;
                    for(uint8_t h = 0; h < 24; h++) {
                        if((p.hours >> h) & 0x1 == 0x1) {
                            hours += String(h, 10) + ",";
                        }
                    }
                    hours = hours.substring(0, hours.length()-1);

                    char start[8];
                    memset(start, 0, 8);
                    if(p.start_dayofmonth > 0 && p.start_month > 0) {
                        snprintf_P(start, 8, PSTR("[%d,%d]"), p.start_month, p.start_dayofmonth);
                    } else {
                        strcpy_P(start, PSTR("null"));
                    }

                    char end[8];
                    memset(end, 0, 8);
                    if(p.end_dayofmonth > 0 && p.end_month > 0) {
                        snprintf_P(end, 8, PSTR("[%d,%d]"), p.end_month, p.end_dayofmonth);
                    } else {
                        strcpy_P(end, PSTR("null"));
                    }

                    pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("{\"type\":%d,\"name\":\"%s\",\"dir\":%d,\"days\":[%s],\"hours\":[%s],\"value\":%.4f,\"start\":%s,\"end\":%s}%s"),
                        p.type,
                        p.name,
                        p.direction,
                        days.c_str(),
                        hours.c_str(),
                        p.value / 10000.0,
                        start,
                        end,
                        i == pc.size()-1 ? "" : ","
                    );
                }
            }
        }
        pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("]}"));
        lastPriceConfig = now;
        sendData = false;
    } else if(lastEac == 0) {
        pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"accounting\":{\"hours\":%d,\"thresholds\":[%d,%d,%d,%d,%d,%d,%d,%d,%d]}"), 
            eac.hours,
            eac.thresholds[0],
            eac.thresholds[1],
            eac.thresholds[2],
            eac.thresholds[3],
            eac.thresholds[4],
            eac.thresholds[5],
            eac.thresholds[6],
            eac.thresholds[7],
            eac.thresholds[8]
        );
        lastEac = now;
    }

    if(sendData) {
        float vcc = 0.0;
        int rssi = 0;
        float temperature = -127;
        if(hw != NULL) {
            vcc = hw->getVcc();
            rssi = hw->getWifiRssi();
            temperature = hw->getTemperature();
        }

        uint8_t espStatus;
        #if defined(ESP8266)
        if(vcc < 2.0) { // Voltage not correct, ESP would not run on this voltage
            espStatus = 1;
        } else if(vcc > 2.8 && vcc < 3.5) {
            espStatus = 1;
        } else if(vcc > 2.7 && vcc < 3.6) {
            espStatus = 2;
        } else {
            espStatus = 3;
        }
        #elif defined(ESP32)
        if(vcc < 2.0) { // Voltage not correct, ESP would not run on this voltage
            espStatus = 1;
        } else if(vcc > 3.1 && vcc < 3.5) {
            espStatus = 1;
        } else if(vcc > 3.0 && vcc < 3.6) {
            espStatus = 2;
        } else {
            espStatus = 3;
        }
        #endif

        uint8_t hanStatus;
        if(data.getLastError() != 0) {
            hanStatus = 3;
        } else if(data.getLastUpdateMillis() == 0 && now < 30000) {
            hanStatus = 0;
        } else if(now - data.getLastUpdateMillis() < 15000) {
            hanStatus = 1;
        } else if(now - data.getLastUpdateMillis() < 30000) {
            hanStatus = 2;
        } else {
            hanStatus = 3;
        }

        uint8_t wifiStatus;
        if(rssi > -75) {
            wifiStatus = 1;
        } else if(rssi > -95) {
            wifiStatus = 2;
        } else {
            wifiStatus = 3;
        }

        uint8_t mqttStatus;
        if(mqttHandler == NULL) {
            mqttStatus = 0;
        } else if(mqttHandler->connected()) {
            mqttStatus = 1;
        } else if(mqttHandler->lastError() == 0) {
            mqttStatus = 2;
        } else {
            mqttStatus = 3;
        }

        pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"data\":{\"clock\":%lu,\"up\":%lu,\"lastUpdate\":%lu,\"est\":%s"), 
            (uint32_t) time(nullptr),
            (uint32_t) (millis64()/1000),
            (uint32_t) (data.getLastUpdateMillis()/1000),
            data.isCounterEstimated() ? "true" : "false"
        );
        if(data.getListType() > 2) {
            pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER_LIST3, "import", data.getActiveImportPower(), data.getReactiveImportPower(), data.getActiveImportCounter(), data.getReactiveImportCounter());
        } else {
            pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER, "import", data.getActiveImportPower(), data.getReactiveImportPower());
        }
        if(data.getListType() > 2) {
            pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER_LIST3, "export", data.getActiveExportPower(), data.getReactiveExportPower(), data.getActiveExportCounter(), data.getReactiveExportCounter());
        } else {
            pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER, "export", data.getActiveExportPower(), data.getReactiveExportPower());
        }
        
        if(data.getListType() > 1) {
            pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"phases\":{"));
            bool first = true;
            if(data.getL1Voltage() > 0.0) {
                if(data.getListType() > 3) {
                    pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 1, data.getL1Voltage(), String(data.getL1Current(), 2).c_str(), data.getL1ActiveImportPower(), data.getL1ActiveExportPower(), data.getL1PowerFactor());
                } else {
                    pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 1, data.getL1Voltage(), String(data.getL1Current(), 2).c_str());
                }
                first = false;
            }
            if(data.getL2Voltage() > 0.0) {
                if(data.getListType() > 3) {
                    pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 2, data.getL2Voltage(), String(data.getL2Current(), 2).c_str(), data.getL2ActiveImportPower(), data.getL2ActiveExportPower(), data.getL2PowerFactor());
                } else {
                    pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 2, data.getL2Voltage(), data.isL2currentMissing() ? "null" : String(data.getL2Current(), 2).c_str());
                }
                first = false;
            }
            if(data.getL3Voltage() > 0.0) {
                if(data.getListType() > 3) {
                    pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 3, data.getL3Voltage(), String(data.getL3Current(), 2).c_str(), data.getL3ActiveImportPower(), data.getL3ActiveExportPower(), data.getL3PowerFactor());
                } else {
                    pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 3, data.getL3Voltage(), String(data.getL3Current(), 2).c_str());
                }
                first = false;
            }
            pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("}"));
        }
        if(data.getListType() > 3) {
            pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"pf\":%.2f"), data.getPowerFactor());
        }

        pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"realtime\":{\"import\":%.3f,\"export\":%.3f}"), ea.getUseThisHour(), ea.getProducedThisHour());
        pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"vcc\":%.2f,\"temp\":%.2f,\"rssi\":%d,\"free\":%d"), vcc, temperature, rssi, ESP.getFreeHeap());
        pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_STATUS, 
            espStatus, 0,
            hanStatus, data.getLastError(),
            wifiStatus, 0,
            mqttStatus, mqttHandler == NULL ? 0 : mqttHandler->lastError()
        );

        pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("}"));
    }
    uint16_t crc = crc16((uint8_t*) clearBuffer, pos);
    pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"crc\":\"%04X\"}"), crc);

    if(rsa == nullptr) {
        #if defined(AMS_REMOTE_DEBUG)
        if (debugger->isActive(RemoteDebug::WARNING))
        #endif
        debugger->printf_P(PSTR("RSA is null\n"));
        return;
    }
    int ret = mbedtls_rsa_check_pubkey(rsa);
    if(ret != 0) {
        #if defined(AMS_REMOTE_DEBUG)
        if (debugger->isActive(RemoteDebug::ERROR))
        #endif
        debugger->printf_P(PSTR("mbedtls_rsa_pkcs1_encrypt return code: %d\n"), ret);
        mbedtls_strerror(ret, clearBuffer, CC_BUF_SIZE);
        #if defined(AMS_REMOTE_DEBUG)
        if (debugger->isActive(RemoteDebug::ERROR))
        #endif
        debugger->printf_P(PSTR("%s\n"), clearBuffer);
        return;
    }
    memset(encryptedBuffer, 0, rsa->len);

    int maxlen = rsa->len - 11; // 11 should be the correct padding size for PKCS1

    Stream *stream = NULL;

    if(config.proto == 0) {
        udp.beginPacket(config.hostname, config.port);
        stream = &udp;
    } else if(config.proto == 1) {
        if(!tcp.connected()) {
            int ret = tcp.connect(config.hostname, config.port, 1000);
            if(ret != 1) {
                #if defined(AMS_REMOTE_DEBUG)
                if (debugger->isActive(RemoteDebug::ERROR))
                #endif
                debugger->printf_P(PSTR("tcp.connect(%s, %d) return code: %d\n"), config.hostname, config.port, ret);
                return;
            }
            tcp.setTimeout((config.interval * 1000) / 2);
        }
        while(tcp.available()) tcp.read(); // Empty incoming buffer
        stream = &tcp;
    } else if(config.proto == 2) {
        if(!http.connected()) {
            http.setReuse(true);
            snprintf_P(clearBuffer, CC_BUF_SIZE, PSTR("http://%s/hub/cloud/data"), config.hostname);
            if(!http.begin(clearBuffer)) {
                #if defined(AMS_REMOTE_DEBUG)
                if (debugger->isActive(RemoteDebug::ERROR))
                #endif
                debugger->printf_P(PSTR("(CloudConnector) Unable to start HTTP connector\n"));
                http.end();
                return;
            }
        }
        if(httpBuffer == NULL) {
            httpBuffer = (uint8_t*) malloc(CC_BUF_SIZE);
        }
    }

    int sendBytes = 0;
    for(int i = 0; i < pos; i += maxlen) {
        int ret = mbedtls_rsa_pkcs1_encrypt(rsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PUBLIC, maxlen, (unsigned char*) (clearBuffer+i), encryptedBuffer);
        if(ret == 0) {
            if(stream != NULL) {
                stream->write(encryptedBuffer, rsa->len);
                stream->flush();
            } else {
                memcpy(httpBuffer + sendBytes, encryptedBuffer, rsa->len);
            }
            sendBytes += rsa->len;
            delay(1);
        } else {
            #if defined(AMS_REMOTE_DEBUG)
            if (debugger->isActive(RemoteDebug::ERROR))
            #endif
            debugger->printf_P(PSTR("mbedtls_rsa_pkcs1_encrypt return code: %d\n"), ret);
            mbedtls_strerror(ret, clearBuffer, CC_BUF_SIZE);
            #if defined(AMS_REMOTE_DEBUG)
            if (debugger->isActive(RemoteDebug::ERROR))
            #endif
            debugger->printf_P(PSTR("%s\n"), clearBuffer);
        }
    }

    if(config.proto == 0) {
        udp.endPacket();
    } else if(config.proto == 1) {
        tcp.write("\r\n");
        tcp.flush();
    } else if(config.proto == 2) {
        http.addHeader("Content-Type", "application/octet-stream");
        int status = http.POST(httpBuffer, sendBytes);
        if(status != 200) {
            #if defined(AMS_REMOTE_DEBUG)
            if (debugger->isActive(RemoteDebug::ERROR))
            #endif
            debugger->printf_P(PSTR("(CloudConnector) Communication error 2, returned status: %d\n"), status);
            #if defined(AMS_REMOTE_DEBUG)
            if (debugger->isActive(RemoteDebug::ERROR))
            #endif
            debugger->printf(http.errorToString(status).c_str());
            debugger->println();
            #if defined(AMS_REMOTE_DEBUG)
            if (debugger->isActive(RemoteDebug::DEBUG))
            #endif
            debugger->printf(http.getString().c_str());

            http.end();
        }
    }

    #if defined(AMS_REMOTE_DEBUG)
    if (debugger->isActive(RemoteDebug::DEBUG))
    #endif
    debugger->printf_P(PSTR("(CloudConnector) %d bytes sent to %s:%d from %s\n"), sendBytes, config.hostname, config.proto == 2 ? 80 : config.port, uuid.c_str());
}

void CloudConnector::forceUpdate() {
    lastUpdate = 0;
    lastPriceConfig = 0;
    lastEac = 0;
}

void CloudConnector::forcePriceUpdate() {
    lastPriceConfig = 0;
}

void CloudConnector::setConnectionHandler(ConnectionHandler* ch) {
	this->ch = ch;
}

void CloudConnector::setPriceConfig(PriceServiceConfig& priceConfig) {
    this->priceConfig = priceConfig;
    this->lastPriceConfig = 0;
}

void CloudConnector::setEnergyAccountingConfig(EnergyAccountingConfig& eac) {
    this->eac = eac;
    this->lastEac = 0;
}

String CloudConnector::generateSeed() {
    uint8_t key[16];
    ESPRandom::uuid4(key);
    seed = ESPRandom::uuidToString(key);
    return seed;
}
