from asyncio.log import logger
import inspect
import json
import logging
from threading import Thread
import requests
import websocket
from bs4 import BeautifulSoup
from senertec.energyUnit import energyUnit
from senertec.lang import lang
from senertec.canipError import canipError
from senertec.canipValue import canipValue
from senertec.board import board
from senertec.datapoint import datapoint


class basesocketclient:
    """Base class which provides logic for a senertec websocket connection."""

    def __init__(self, level=logging.WARN):
        self.logger = logging.getLogger(__name__)
        self.logger.setLevel(level)
        self.WS_HOST = "wss://dachsconnect.senertec.com/ws"
        self.__is_ws_connected__ = False
        self.__messages__ = [str()]
        self.__thread__ = None
        self.ws = None
        self.boards = [board()]
        """All available boards with datapoints which can be used in request function"""

    def __on_message__(self, ws, message):
        j = json.loads(message)
        action = j["action"]
        data = j["data"]
        if action == "CanipValue":
            self.logger.debug("Received new CanipValue from websocket.")
            for b in self.boards:
                if b.boardName == data["boardName"]:
                    value = canipValue()
                    value.boardName = b.boardName
                    for point in b.datapoints:
                        if point.id == data["dataPointName"]:
                            value.friendlyDataName = point.friendlyName
                            value.sourceDatapoint = point.sourceId
                            tempValue = data["value"]
                            if point.enumName != None:
                                for enum in self.__enumTranslations__:
                                    if point.enumName == enum["name"]:
                                        try:
                                            value.dataValue = enum["translations"][f"{tempValue}"]
                                        except KeyError:
                                            self.logger.warning(
                                                f"No enum translation found for datapoint '{point.friendlyName}'.")
                                            value.dataValue = "Unknown"
                            elif point.gain != 0 and point.gain != 1:
                                value.dataValue = tempValue * \
                                    point.gain
                            else:
                                value.dataValue = tempValue
                            if point.unit != None:
                                value.dataUnit = point.unit
                            else:
                                value.dataUnit = ""
                            self.messagecallback(value)
                            break
        # reconnect
        elif action == "HkaStore" and data["updateType"] == "remove":
            sn = j["sn"]
            self.logger.info(
                f"Unit with serial {sn} got disconnected.")
        else:
            self.logger.debug(f"Received new websocket message {action}.")
            self.__messages__.append(message)

    def __on_error__(self, ws, error):
        self.logger.error(f"error : {error}")

    def __on_close__(self, ws, close_status_code, close_msg):
        self.logger.info(
            f"Senertec Websocket closed with code {close_status_code}")
        self.__is_ws_connected__ = False

    def __on_open__(self, ws):
        self.logger.info("Connected to Senertec websocket")
        self.__is_ws_connected__ = True

    def __create_websocket__(self):
        cookies = self.__getCookies__(
            self.__session__.cookies, "dachsconnect.senertec.com")
        self.logger.debug("Creating websocket connection..")
        self.ws = websocket.WebSocketApp(self.WS_HOST,
                                         on_message=self.__on_message__,
                                         on_error=self.__on_error__,
                                         on_close=self.__on_close__,
                                         on_open=self.__on_open__,
                                         cookie=cookies)
        self.__thread__ = Thread(
            target=self.ws.run_forever, kwargs={
                "ping_interval": 60, "ping_timeout": 5}
        )
        self.__thread__.daemon = True
        self.__thread__.setName("senertec-websocket")
        self.__thread__.start()
        self.logger.debug("Websocket connection started.")


class senertec(basesocketclient):
    """Class to communicate with Senertec and handle network calls"""

    def __init__(self, dataNames=None, email: str = None, password: str = None, language=lang.English, level=logging.INFO):
        """Constructor, create instance of senertec client.

        ``dataNames`` Json string of the productGroups.json file.

        ``language`` Set to your language.
        """
        if email is None or password is None or dataNames is None:
            raise ValueError(
                "Arguments 'email', 'passwords', dataNames are required"
            )
        super().__init__(level)
        logging.basicConfig(
            level=level,
            format='py-senertec: %(asctime)s %(levelname)-8s %(message)s',
            datefmt='%Y-%m-%d %H:%M:%S'
        )
        self.AUTHENTICATION_HOST = "https://dachsconnect.senertec.com"
        self.SSO_HOST = "https://sso-portal.senertec.com"
        self.email = email
        self.__session__ = None
        self.language = language
        self.password = password
        self.level = level
        self.__supportedItems__ = dataNames
        self.__enums__ = []
        self.__metaDataPoints__ = []
        self.__metaDataTranslations__ = []
        self.__enumTranslations__ = []
        self.__errorTranslations__ = []
        self.__connectedUnit__ = []
        self.messagecallback = (canipValue())
        """Set your callback function to get the data values. Function has to be overloaded with data type ``canipValue``"""
        self.logger = logging.getLogger(__name__)
        self.logger.setLevel(level)

    def __create_headers__(self):
        headers = {"Content-Type": "application/json"}
        return headers

    def __getCookies__(self, cookie_jar, domain):
        cookie_dict = cookie_jar.get_dict(domain=domain)
        found = ['%s=%s' % (name, value)
                 for (name, value) in cookie_dict.items()]
        return ';'.join(found)

    def __post__(self, urlPath: str, payload: str):
        url = self.AUTHENTICATION_HOST + urlPath
        response = self.__session__.post(
            url, data=payload, headers=self.__create_headers__())
        if(response.status_code >= 400 and response.status_code <= 599):
            logger.error("Error in post request by function: " + inspect.stack()
                         [1].function + " HTTP response: " + response.text)
        return response

    def __get__(self, urlPath: str):
        url = self.AUTHENTICATION_HOST + urlPath
        response = self.__session__.get(
            url, headers=self.__create_headers__())
        if(response.status_code >= 400 and response.status_code <= 599):
            logger.error("Error in get request by function: {" + inspect.stack()
                         [1].function + "} HTTP response: " + response.text)
        return response

    def __parsedatapoints__(self):
        self.logger.debug("Starting to parse datapoints..")
        metaData = self.__metaDataPoints__
        blist = []
        dataPointCount = 0
        boardname = ""
        for a in metaData:
            for element in self.__supportedItems__[self.__connectedUnit__["productGroup"]]:
                if metaData[a]["friendlyName"] == element:
                    for l in self.__connectedUnit__["boards"]:
                        for o in l["attributes"]:
                            if o == metaData[a]["name"]:
                                boardname = l["name"]
                    datap = datapoint()
                    datap.sourceId = element
                    datap.id = metaData[a]["name"]
                    datap.friendlyName = self.__metaDataTranslations__[
                        metaData[a]["name"]]
                    datap.unit = metaData[a]["unit"]
                    datap.gain = metaData[a]["gain"]
                    datap.enumName = metaData[a]["enumName"]
                    dataPointCount += 1
                    # avoid doubled board entries
                    if not any(x for x in blist if x.boardName == boardname):
                        b = board()
                        b.boardName = boardname
                        b.datapoints.append(datap)
                        blist.append(b)
                    else:
                        for b in blist:
                            if b.boardName == boardname:
                                b.datapoints.append(datap)
        self.boards = blist
        self.logger.debug(
            f"Parsing datapoints finished, found {len(blist)} boards with {dataPointCount} datapoints.")

    def login(self):
        """
        Login.

        This function needs to be called first.

        Returns True on success, False on failure.
        """
        self.logger.info("Logging in..")
        self.__session__ = requests.Session()
        loginSSOResponse = self.__session__.get(
            self.AUTHENTICATION_HOST + "/rest/saml2/login")

        authState = loginSSOResponse.url.split("loginuserpass.php?")[1]
        head = {"Content-Type": "application/x-www-form-urlencoded"}
        userData = f"username={self.email}&password={<PASSWORD>}&{authState}"
        # submit credentials
        loginResponse = self.__session__.post(self.SSO_HOST + "/simplesaml/module.php/core/loginuserpass.php?",
                                              data=userData, headers=head)

        # filter out samlresponse and relaystate for ACS request
        soup = BeautifulSoup(loginResponse.text, features="html.parser")
        try:
            samlResponse = soup.findAll(
                "input", {"name": "SAMLResponse"})[0]["value"]
            relayState = soup.findAll(
                "input", {"name": "RelayState"})[0]["value"]
        except IndexError:
            self.logger.error("Login failed, username or password wrong.")
            return False
        acsData = {'SAMLResponse': {samlResponse}, 'RelayState': {relayState}}

        # do assertion consumer service request with received saml response
        acs = self.__session__.post(
            self.AUTHENTICATION_HOST + "/rest/saml2/acs", data=acsData, headers=head)

        if acs.history[0].status_code != 302:
            self.logger.error("Login failed at ACS request, got no redirect.")
            return False
        self.logger.info("Login was successful.")
        return True

    def logout(self):
        """
        Logout from senertec.

        Returns True on success, False on failure.
        """
        self.logger.info("Logging out..")
        response = self.__get__("/logout")
        if self.ws:
            self.ws.close()
        self.__session__.close()
        if response.status_code == 200:
            self.logger.debug("Logout was successful.")
            return True
        else:
            return False

    def init(self):
        """
        Initialize Senertec platform and connect to websocket.

        This function needs to be called after login.

        Returns True on success, False on failure.
        """
        self.logger.info("Initializing senertec platform...")
        response = self.__get__("/rest/info/init")
        if response.status_code == 200:
            self.__create_websocket__()
            j = json.loads(response.text)
            self.__metaDataPoints__ = j["metaDataPoints"]
            self.__enums__ = j["enums"]
            self.__enumTranslations__ = j["translations"][f"{self.language.value}"]["enums"]
            self.__metaDataTranslations__ = j["translations"][
                f"{self.language.value}"]["metaDataPoints"]["translations"]
            self.__errorTranslations__ = j["translations"][f"{self.language.value}"]["errorCategories"]
            return True
        else:
            return False

    def getUnits(self) -> list[energyUnit]:
        """
        Get all units.

        Returns all senertec products of this account as list.
        """
        response = self.__post__(
            "/rest/info/units", json.dumps({"limit": 10, "offset": 0, "filter": {}}))
        if response.status_code == 200:
            values = json.loads(response.text)
            units = []
            for x in values["units"]:
                unit = energyUnit()
                unit.model = x["benennung"]
                unit.serial = x["seriennummer"]
                units.append(unit)
            self.logger.debug(
                f"Successful received a list of {len(units)} units.")
            return units
        else:
            return None

    def connectUnit(self, serial: str):
        """
        Connect to a unit.

        This function connects to a unit and enables receiving data for that unit.

        ``serial`` Serial number of energy unit. Can be received with getUnits() method.

        Returns True on success, False on failure.
        """
        response = self.__get__(f"/rest/canip/{serial}")
        if response.status_code == 200:
            self.__connectedUnit__ = json.loads(response.text)
            self.__parsedatapoints__()
            return True
        else:
            return False

    def disconnectUnit(self):
        """
        Disconnect from a unit.

        This function disconnects from a unit.

        Returns True on success, False on failure.
        """
        sn = self.__connectedUnit__["seriennummer"]
        response = self.__get__(f"/rest/canip/{sn}/disconnect")
        if response.status_code == 200:
            return True
        else:
            return False

    def getChart(self, chartname: str):
        """
        Get a history chart of the connected unit.
        """
        sn = self.__connectedUnit__["seriennummer"]
        response = self.__post__(
            f"/rest/charts/{sn}/data", json.dumps(
                {"start": 1641596400000, "end": None, "parameters": {}, "chartName": chartname, "sn": sn}))
        if response.status_code == 200:
            return json.loads(response.text)

    def request(self, dataPoints: list):
        """
        Request data from specific data points of the connected unit.

        Data wil be received through websocket.

        ``dataPoints`` List of datapoint strings

        Returns True on success, False on failure.
        """
        sn = self.__connectedUnit__["seriennummer"]
        j = json.dumps(
            {"seriennummer": sn, "keys": dataPoints})
        response = self.__post__(
            f"/rest/canip/{sn}/request", j)
        result = json.loads(response.text)
        if response.status_code == 200 and result["success"] and not result["errorKeys"]:
            return True
        else:
            return False

    def getBoardList(self):
        """Get all boards of the connected unit"""
        lst = [str()]
        for b in self.__connectedUnit__["boards"]:
            lst.append(b["name"])
        return lst

    def getErrors(self, onlyCurrentErrors: bool = True) -> list[canipError]:
        """Get an error history of the connected unit. 

        This gets loaded when a unit gets connected."""
        lst = []
        for b in self.__connectedUnit__["boards"]:
            for e in b["canipErrors"]:
                if onlyCurrentErrors and not e["currentError"]:
                    continue
                error = canipError()
                error.boardName = e["boardName"]
                error.code = e["codeText"]
                error.counter = e["counter"]
                error.currentError = e["currentError"]
                error.timestamp = e["timestamp"]
                cat = e["errorCode"]["category"]
                code = e["errorCode"]["code"]
                error.errorCategory = self.__errorTranslations__[
                    f"{cat}"]["translationMedium"]
                error.errorTranslation = self.__errorTranslations__[
                    f"{cat}"]["translations"][f"{code}"]
                lst.append(error)
        return lst

from asyncio.log import logger
import inspect
import json
import logging
from threading import Thread
import requests
import websocket
from bs4 import BeautifulSoup
from senertec.energyUnit import energyUnit
from senertec.lang import lang
from senertec.canipError import canipError
from senertec.canipValue import canipValue
from senertec.board import board
from senertec.datapoint import datapoint


class basesocketclient:
    """Base class which provides logic for a senertec websocket connection."""

    def __init__(self, level=logging.WARN):
        self.logger = logging.getLogger(__name__)
        self.logger.setLevel(level)
        self.WS_HOST = "wss://dachsconnect.senertec.com/ws"
        self.__is_ws_connected__ = False
        self.__messages__ = [str()]
        self.__thread__ = None
        self.ws = None
        self.boards = [board()]
        """All available boards with datapoints which can be used in request function"""

    def __on_message__(self, ws, message):
        j = json.loads(message)
        action = j["action"]
        data = j["data"]
        if action == "CanipValue":
            self.logger.debug("Received new CanipValue from websocket.")
            for b in self.boards:
                if b.boardName == data["boardName"]:
                    value = canipValue()
                    value.boardName = b.boardName
                    for point in b.datapoints:
                        if point.id == data["dataPointName"]:
                            value.friendlyDataName = point.friendlyName
                            value.sourceDatapoint = point.sourceId
                            tempValue = data["value"]
                            if point.enumName != None:
                                for enum in self.__enumTranslations__:
                                    if point.enumName == enum["name"]:
                                        try:
                                            value.dataValue = enum["translations"][f"{tempValue}"]
                                        except KeyError:
                                            self.logger.warning(
                                                f"No enum translation found for datapoint '{point.friendlyName}'.")
                                            value.dataValue = "Unknown"
                            elif point.gain != 0 and point.gain != 1:
                                value.dataValue = tempValue * \
                                    point.gain
                            else:
                                value.dataValue = tempValue
                            if point.unit != None:
                                value.dataUnit = point.unit
                            else:
                                value.dataUnit = ""
                            self.messagecallback(value)
                            break
        # reconnect
        elif action == "HkaStore" and data["updateType"] == "remove":
            sn = j["sn"]
            self.logger.info(
                f"Unit with serial {sn} got disconnected.")
        else:
            self.logger.debug(f"Received new websocket message {action}.")
            self.__messages__.append(message)

    def __on_error__(self, ws, error):
        self.logger.error(f"error : {error}")

    def __on_close__(self, ws, close_status_code, close_msg):
        self.logger.info(
            f"Senertec Websocket closed with code {close_status_code}")
        self.__is_ws_connected__ = False

    def __on_open__(self, ws):
        self.logger.info("Connected to Senertec websocket")
        self.__is_ws_connected__ = True

    def __create_websocket__(self):
        cookies = self.__getCookies__(
            self.__session__.cookies, "dachsconnect.senertec.com")
        self.logger.debug("Creating websocket connection..")
        self.ws = websocket.WebSocketApp(self.WS_HOST,
                                         on_message=self.__on_message__,
                                         on_error=self.__on_error__,
                                         on_close=self.__on_close__,
                                         on_open=self.__on_open__,
                                         cookie=cookies)
        self.__thread__ = Thread(
            target=self.ws.run_forever, kwargs={
                "ping_interval": 60, "ping_timeout": 5}
        )
        self.__thread__.daemon = True
        self.__thread__.setName("senertec-websocket")
        self.__thread__.start()
        self.logger.debug("Websocket connection started.")


class senertec(basesocketclient):
    """Class to communicate with Senertec and handle network calls"""

    def __init__(self, dataNames=None, email: str = None, password: str = None, language=lang.English, level=logging.INFO):
        """Constructor, create instance of senertec client.

        ``dataNames`` Json string of the productGroups.json file.

        ``language`` Set to your language.
        """
        if email is None or password is None or dataNames is None:
            raise ValueError(
                "Arguments 'email', 'passwords', dataNames are required"
            )
        super().__init__(level)
        logging.basicConfig(
            level=level,
            format='py-senertec: %(asctime)s %(levelname)-8s %(message)s',
            datefmt='%Y-%m-%d %H:%M:%S'
        )
        self.AUTHENTICATION_HOST = "https://dachsconnect.senertec.com"
        self.SSO_HOST = "https://sso-portal.senertec.com"
        self.email = email
        self.__session__ = None
        self.language = language
        self.password = password
        self.level = level
        self.__supportedItems__ = dataNames
        self.__enums__ = []
        self.__metaDataPoints__ = []
        self.__metaDataTranslations__ = []
        self.__enumTranslations__ = []
        self.__errorTranslations__ = []
        self.__connectedUnit__ = []
        self.messagecallback = (canipValue())
        """Set your callback function to get the data values. Function has to be overloaded with data type ``canipValue``"""
        self.logger = logging.getLogger(__name__)
        self.logger.setLevel(level)

    def __create_headers__(self):
        headers = {"Content-Type": "application/json"}
        return headers

    def __getCookies__(self, cookie_jar, domain):
        cookie_dict = cookie_jar.get_dict(domain=domain)
        found = ['%s=%s' % (name, value)
                 for (name, value) in cookie_dict.items()]
        return ';'.join(found)

    def __post__(self, urlPath: str, payload: str):
        url = self.AUTHENTICATION_HOST + urlPath
        response = self.__session__.post(
            url, data=payload, headers=self.__create_headers__())
        if(response.status_code >= 400 and response.status_code <= 599):
            logger.error("Error in post request by function: " + inspect.stack()
                         [1].function + " HTTP response: " + response.text)
        return response

    def __get__(self, urlPath: str):
        url = self.AUTHENTICATION_HOST + urlPath
        response = self.__session__.get(
            url, headers=self.__create_headers__())
        if(response.status_code >= 400 and response.status_code <= 599):
            logger.error("Error in get request by function: {" + inspect.stack()
                         [1].function + "} HTTP response: " + response.text)
        return response

    def __parsedatapoints__(self):
        self.logger.debug("Starting to parse datapoints..")
        metaData = self.__metaDataPoints__
        blist = []
        dataPointCount = 0
        boardname = ""
        for a in metaData:
            for element in self.__supportedItems__[self.__connectedUnit__["productGroup"]]:
                if metaData[a]["friendlyName"] == element:
                    for l in self.__connectedUnit__["boards"]:
                        for o in l["attributes"]:
                            if o == metaData[a]["name"]:
                                boardname = l["name"]
                    datap = datapoint()
                    datap.sourceId = element
                    datap.id = metaData[a]["name"]
                    datap.friendlyName = self.__metaDataTranslations__[
                        metaData[a]["name"]]
                    datap.unit = metaData[a]["unit"]
                    datap.gain = metaData[a]["gain"]
                    datap.enumName = metaData[a]["enumName"]
                    dataPointCount += 1
                    # avoid doubled board entries
                    if not any(x for x in blist if x.boardName == boardname):
                        b = board()
                        b.boardName = boardname
                        b.datapoints.append(datap)
                        blist.append(b)
                    else:
                        for b in blist:
                            if b.boardName == boardname:
                                b.datapoints.append(datap)
        self.boards = blist
        self.logger.debug(
            f"Parsing datapoints finished, found {len(blist)} boards with {dataPointCount} datapoints.")

    def login(self):
        """
        Login.

        This function needs to be called first.

        Returns True on success, False on failure.
        """
        self.logger.info("Logging in..")
        self.__session__ = requests.Session()
        loginSSOResponse = self.__session__.get(
            self.AUTHENTICATION_HOST + "/rest/saml2/login")

        authState = loginSSOResponse.url.split("loginuserpass.php?")[1]
        head = {"Content-Type": "application/x-www-form-urlencoded"}
        userData = f"username={self.email}&password={<PASSWORD>}&{authState}"
        # submit credentials
        loginResponse = self.__session__.post(self.SSO_HOST + "/simplesaml/module.php/core/loginuserpass.php?",
                                              data=userData, headers=head)

        # filter out samlresponse and relaystate for ACS request
        soup = BeautifulSoup(loginResponse.text, features="html.parser")
        try:
            samlResponse = soup.findAll(
                "input", {"name": "SAMLResponse"})[0]["value"]
            relayState = soup.findAll(
                "input", {"name": "RelayState"})[0]["value"]
        except IndexError:
            self.logger.error("Login failed, username or password wrong.")
            return False
        acsData = {'SAMLResponse': {samlResponse}, 'RelayState': {relayState}}

        # do assertion consumer service request with received saml response
        acs = self.__session__.post(
            self.AUTHENTICATION_HOST + "/rest/saml2/acs", data=acsData, headers=head)

        if acs.history[0].status_code != 302:
            self.logger.error("Login failed at ACS request, got no redirect.")
            return False
        self.logger.info("Login was successful.")
        return True

    def logout(self):
        """
        Logout from senertec.

        Returns True on success, False on failure.
        """
        self.logger.info("Logging out..")
        response = self.__get__("/logout")
        if self.ws:
            self.ws.close()
        self.__session__.close()
        if response.status_code == 200:
            self.logger.debug("Logout was successful.")
            return True
        else:
            return False

    def init(self):
        """
        Initialize Senertec platform and connect to websocket.

        This function needs to be called after login.

        Returns True on success, False on failure.
        """
        self.logger.info("Initializing senertec platform...")
        response = self.__get__("/rest/info/init")
        if response.status_code == 200:
            self.__create_websocket__()
            j = json.loads(response.text)
            self.__metaDataPoints__ = j["metaDataPoints"]
            self.__enums__ = j["enums"]
            self.__enumTranslations__ = j["translations"][f"{self.language.value}"]["enums"]
            self.__metaDataTranslations__ = j["translations"][
                f"{self.language.value}"]["metaDataPoints"]["translations"]
            self.__errorTranslations__ = j["translations"][f"{self.language.value}"]["errorCategories"]
            return True
        else:
            return False

    def getUnits(self) -> list[energyUnit]:
        """
        Get all units.

        Returns all senertec products of this account as list.
        """
        response = self.__post__(
            "/rest/info/units", json.dumps({"limit": 10, "offset": 0, "filter": {}}))
        if response.status_code == 200:
            values = json.loads(response.text)
            units = []
            for x in values["units"]:
                unit = energyUnit()
                unit.model = x["benennung"]
                unit.serial = x["seriennummer"]
                units.append(unit)
            self.logger.debug(
                f"Successful received a list of {len(units)} units.")
            return units
        else:
            return None

    def connectUnit(self, serial: str):
        """
        Connect to a unit.

        This function connects to a unit and enables receiving data for that unit.

        ``serial`` Serial number of energy unit. Can be received with getUnits() method.

        Returns True on success, False on failure.
        """
        response = self.__get__(f"/rest/canip/{serial}")
        if response.status_code == 200:
            self.__connectedUnit__ = json.loads(response.text)
            self.__parsedatapoints__()
            return True
        else:
            return False

    def disconnectUnit(self):
        """
        Disconnect from a unit.

        This function disconnects from a unit.

        Returns True on success, False on failure.
        """
        sn = self.__connectedUnit__["seriennummer"]
        response = self.__get__(f"/rest/canip/{sn}/disconnect")
        if response.status_code == 200:
            return True
        else:
            return False

    def getChart(self, chartname: str):
        """
        Get a history chart of the connected unit.
        """
        sn = self.__connectedUnit__["seriennummer"]
        response = self.__post__(
            f"/rest/charts/{sn}/data", json.dumps(
                {"start": 1641596400000, "end": None, "parameters": {}, "chartName": chartname, "sn": sn}))
        if response.status_code == 200:
            return json.loads(response.text)

    def request(self, dataPoints: list):
        """
        Request data from specific data points of the connected unit.

        Data wil be received through websocket.

        ``dataPoints`` List of datapoint strings

        Returns True on success, False on failure.
        """
        sn = self.__connectedUnit__["seriennummer"]
        j = json.dumps(
            {"seriennummer": sn, "keys": dataPoints})
        response = self.__post__(
            f"/rest/canip/{sn}/request", j)
        result = json.loads(response.text)
        if response.status_code == 200 and result["success"] and not result["errorKeys"]:
            return True
        else:
            return False

    def getBoardList(self):
        """Get all boards of the connected unit"""
        lst = [str()]
        for b in self.__connectedUnit__["boards"]:
            lst.append(b["name"])
        return lst

    def getErrors(self, onlyCurrentErrors: bool = True) -> list[canipError]:
        """Get an error history of the connected unit. 

        This gets loaded when a unit gets connected."""
        lst = []
        for b in self.__connectedUnit__["boards"]:
            for e in b["canipErrors"]:
                if onlyCurrentErrors and not e["currentError"]:
                    continue
                error = canipError()
                error.boardName = e["boardName"]
                error.code = e["codeText"]
                error.counter = e["counter"]
                error.currentError = e["currentError"]
                error.timestamp = e["timestamp"]
                cat = e["errorCode"]["category"]
                code = e["errorCode"]["code"]
                error.errorCategory = self.__errorTranslations__[
                    f"{cat}"]["translationMedium"]
                error.errorTranslation = self.__errorTranslations__[
                    f"{cat}"]["translations"][f"{code}"]
                lst.append(error)
        return lst

Base class which provides logic for a senertec websocket connection. All available boards with datapoints which can be used in request function # reconnect Class to communicate with Senertec and handle network calls Constructor, create instance of senertec client.
 
         ``dataNames`` Json string of the productGroups.json file.
 
         ``language`` Set to your language. Set your callback function to get the data values. Function has to be overloaded with data type ``canipValue`` # avoid doubled board entries Login.
 
         This function needs to be called first.
 
         Returns True on success, False on failure. # submit credentials # filter out samlresponse and relaystate for ACS request # do assertion consumer service request with received saml response Logout from senertec.
 
         Returns True on success, False on failure. Initialize Senertec platform and connect to websocket.
 
         This function needs to be called after login.
 
         Returns True on success, False on failure. Get all units.
 
         Returns all senertec products of this account as list. Connect to a unit.
 
         This function connects to a unit and enables receiving data for that unit.
 
         ``serial`` Serial number of energy unit. Can be received with getUnits() method.
 
         Returns True on success, False on failure. Disconnect from a unit.
 
         This function disconnects from a unit.
 
         Returns True on success, False on failure. Get a history chart of the connected unit. Request data from specific data points of the connected unit.
 
         Data wil be received through websocket.
 
         ``dataPoints`` List of datapoint strings
 
         Returns True on success, False on failure. Get all boards of the connected unit Get an error history of the connected unit. 
 
         This gets loaded when a unit gets connected.