Esempio n. 1
0
 def set_server_transport(self, value):
     """save the GpsGate server transport - ('tcp' or 'xml')"""
     value = clean_string(value).lower()
     if self.gps_gate_transport != value:
         if value == 'xml':
             raise NotImplementedError("GpsGate XML transport - not yet")
         elif value != 'tcp':
             raise ValueError("GpsGate - only TCP transport supported")
         self.gps_gate_transport = value
         self.logger.info("GpsGate: Setting Transport:{}".format(value))
Esempio n. 2
0
    def set_username(self, value: str):
        """
        Set the user-name used with GpsGate Server. There don't seem to
        be any 'rules' about what makes a valid user-name.

        Insure is string, remove extra quotes, etc.
        """
        value = clean_string(value)
        if self.client_username != value:
            self.client_username = value
            self.logger.info("GpsGate: Setting user name:{}".format(value))
Esempio n. 3
0
def run_router_app(app_base, adjust_seconds):
    """

    :param CradlepointAppBase app_base: prepared resources: logger, cs_client
    :param float adjust_seconds:
    :return:
    """

    replay_file_name = DEF_REPLAY_FILE

    section = "gps"
    if section in app_base.settings:
        # then load dynamic values
        temp = app_base.settings[section]

        if "replay_file" in temp:
            replay_file_name = clean_string(temp["replay_file"])

    if not os.path.isfile(replay_file_name):
        app_base.logger.error(
            "Replay file({}) not found".format(replay_file_name))
        raise FileNotFoundError

    app_base.logger.info(
        "Replay file is named:{}".format(replay_file_name))

    backup_file_name = replay_file_name + '.bak'
    app_base.logger.info(
        "Backup file is named:{}".format(backup_file_name))

    os.rename(src=replay_file_name, dst=backup_file_name)

    file_in = open(backup_file_name, "r")
    file_out = open(replay_file_name, "w")

    while True:
        # loop forever

        # this is a dictionary
        dict_in = read_in_line(file_in)
        if dict_in is None:
            app_base.logger.warning("Close Replay file")
            break

        else:
            dict_in["offset"] += adjust_seconds
            result = '{"offset":%d, "data":"%s"},\n' % (dict_in["offset"],
                                                        dict_in["data"])
            file_out.write(result)

    file_in.close()
    file_out.close()

    return 0
Esempio n. 4
0
    def set_client_name(self, name: str, version=None):
        """
        Set client name. if version is none, then we assume name is like
        "my tool 1.4" 7 try to parse off the ending float.

        TODO At the moment, this routine assumes version is like x.y, with y
        being only 1 digit. So 1.2 is okay, but 1.22 is not
        """
        change = False

        name = clean_string(name)
        if version is None:
            # assume version is on end of name!
            name = name.split(' ')
            if len(name) <= 1:
                # then is a single work/token - assume version 1.0
                version = 1.0
                name = name[0]
            else:
                if name[-1].find('.') >= 0:
                    version = name[-1]
                    name.pop(-1)
                else:
                    version = 1.0
                name = ' '.join(name)

        if isinstance(version, int):
            version = float(version)

        if isinstance(version, float):
            version = str(version)

        if self.client_name != name:
            self.client_name = name
            change = True

        if self.client_version != version:
            self.client_version = version
            change = True

        if change and self.logger is not None:
            self.logger.info(
                "GpsGate: Setting client name:{} version:{}".format(
                    self.client_name, self.client_version))
Esempio n. 5
0
def run_router_app(app_base):
    """

    :param CradlepointAppBase app_base: prepared resources: logger, cs_client
    :return:
    """
    # logger.debug("Settings({})".format(sets))

    # handle the GPS_GATE protocol
    gps_gate = GpsGate(app_base.logger)

    # process the SETTINGS file
    section = "gps_gate"
    if section not in app_base.settings:
        # this is unlikely!
        app_base.logger.warning(
            "Aborting: No [%s] section in settings.ini".format(section))
        return -1

    else:
        # then load dynamic values
        temp = app_base.settings[section]

        app_base.logger.debug("[{}] section = {}".format(section, temp))

        # settings for our LISTENER for router GPS output
        buffer_size = int(temp.get("buffer_size", DEF_BUFFER_SIZE))

        # check on our localhost port (not used, but to test)
        config = GpsConfig(app_base)
        host_ip, host_port = config.get_client_info()
        if "host_ip" in temp:
            # then OVER-RIDE what the router told us
            app_base.logger.warning("Settings OVER-RIDE router host_ip")
            value = clean_string(temp["host_ip"])
            app_base.logger.warning("was:{} now:{}".format(host_ip, value))
            host_ip = value

        if "host_port" in temp:
            # then OVER-RIDE what the router told us
            app_base.logger.warning("Settings OVER-RIDE router host_port")
            value = parse_integer(temp["host_port"])
            app_base.logger.warning("was:{} now:{}".format(host_port, value))
            host_port = value

        app_base.logger.debug("GPS source:({}:{})".format(host_ip, host_port))
        del config

        # settings defining the GpsGate interactions
        if "gps_gate_url" in temp:
            gps_gate.set_server_url(temp["gps_gate_url"])
        if "gps_gate_port" in temp:
            gps_gate.set_server_port(temp["gps_gate_port"])
        if "gps_gate_transport" in temp:
            gps_gate.set_server_transport(temp["gps_gate_transport"])

        if "username" in temp:
            gps_gate.set_username(temp["username"])
        if "password" in temp:
            gps_gate.set_password(temp["password"])
        if "server_version" in temp:
            gps_gate.set_server_version(temp["server_version"])
        if "client" in temp:
            gps_gate.set_client_name(temp["client"])

        if "IMEI" in temp:
            gps_gate.set_imei(temp["IMEI"])
        elif "imei" in temp:
            # handle upper or lower case, just because ...
            gps_gate.set_imei(temp["imei"])

        # we can pre-set the filters here, but Gpsgate may try to override
        if "distance_filter" in temp:
            gps_gate.nmea.set_distance_filter(temp["distance_filter"])
        if "time_filter" in temp:
            gps_gate.nmea.set_time_filter(temp["time_filter"])
        if "speed_filter" in temp:
            gps_gate.nmea.set_speed_filter(temp["speed_filter"])
        if "direction_filter" in temp:
            gps_gate.nmea.set_direction_filter(temp["direction_filter"])
        if "direction_threshold" in temp:
            gps_gate.nmea.set_direction_threshold(temp["direction_threshold"])

    # check the cell modem/gps sources
    wan_data = ActiveWan(app_base)
    # app_base.logger.info("WAN data {}".format(wan_data['active']))

    value = wan_data.get_imei()
    if gps_gate.client_imei is None:
        # only take 'live' value if setting.ini was NOT included
        gps_gate.set_imei(value)
    else:
        app_base.logger.warning(
            "Using settings.ini IMEI, ignoring cell modem's value")

    if not wan_data.supports_gps():
        app_base.logger.warning(
            "cell modem claims no GPS - another source might exist")

    if wan_data.get_live_gps_data() in (None, {}):
        app_base.logger.warning("cell modem has no last GPS data")
    else:
        app_base.logger.debug("GPS={}".format(wan_data.get_live_gps_data()))

    while True:
        # define the socket resource, including the type (stream == "TCP")
        address = (host_ip, host_port)
        app_base.logger.info("Preparing GPS Listening on {}".format(address))
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        # attempt to actually lock resource, which may fail if unavailable
        #   (see BIND ERROR note)
        try:
            sock.bind(address)
        except OSError as msg:
            app_base.logger.error("socket.bind() failed - {}".format(msg))

            # technically, Python will close when 'sock' goes out of scope,
            # but be disciplined and close it yourself. Python may warning
            # you of unclosed resource, during runtime.
            try:
                sock.close()
            except OSError:
                pass

            # we exit, because if we cannot secure the resource, the errors
            # are likely permanent.
            return -1

        # only allow 1 client at a time
        sock.listen(3)

        while True:
            # loop forever
            app_base.logger.info("Waiting on TCP socket %d" % host_port)
            client, address = sock.accept()
            app_base.logger.info("Accepted connection from {}".format(address))

            # for cellular, ALWAYS enable TCP Keep Alive (see KEEP ALIVE note)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)

            # set non-blocking so we can do a manual timeout (use of select()
            # is better ... but that's another sample)
            # client.setblocking(0)

            while True:
                app_base.logger.debug("Waiting to receive data")
                data = client.recv(buffer_size)
                # data is type() bytes, to echo we don't need to convert
                # to str to format or return.
                if data:
                    data = data.decode().split()

                    # gps.start()
                    for line in data:
                        try:
                            pass
                            # result = gps.parse_sentence(line)
                            # if not result:
                            #     break

                        except ValueError:
                            app_base.logger.warning(
                                "Bad NMEA sentence:[{}]".format(line))
                            raise

                    # gps.publish()
                    # app_base.logger.debug(
                    #     "See({})".format(gps.get_attributes()))
                    # client.send(data)
                else:
                    break

                time.sleep(1.0)

            app_base.logger.info("Client disconnected")
            client.close()

            # since this server is expected to run on a small embedded system,
            # free up memory ASAP (see MEMORY note)
            del client
            gc.collect()

    return 0
Esempio n. 6
0
def run_client():
    global base_app

    obj = protocol.GpsGate(base_app.logger)

    # base_app.logger.debug("sets:{}".format(base_app.settings))

    # set a few defaults
    imei = "353547060660845"
    gps_gate_url = "64.46.40.178"
    gps_gate_port = 30175

    if "gps_gate" in base_app.settings:
        # then we do have a customer config
        temp = base_app.settings["gps_gate"]

        # check on our localhost port (not used, but to test)
        config = GpsConfig(base_app)
        host_ip, host_port = config.get_client_info()
        if "host_ip" in temp:
            # then OVER-RIDE what the router told us
            base_app.logger.warning("Settings OVER-RIDE router host_ip")
            value = clean_string(temp["host_ip"])
            base_app.logger.warning("was:{} now:{}".format(host_ip, value))
            host_ip = value

        if "host_port" in temp:
            # then OVER-RIDE what the router told us
            base_app.logger.warning("Settings OVER-RIDE router host_port")
            value = parse_integer(temp["host_port"])
            base_app.logger.warning("was:{} now:{}".format(host_port, value))
            host_port = value

        base_app.logger.debug("GPS source:({}:{})".format(host_ip, host_port))
        del config

        # check on our cellular details
        config = ActiveWan(base_app)
        imei = config.get_imei()
        if "imei" in temp:
            # then OVER-RIDE what the router told us
            base_app.logger.warning("Settings OVER-RIDE router IMEI")
            value = clean_string(temp["imei"])
            base_app.logger.warning("was:{} now:{}".format(imei, value))
            imei = value
        del config

        if "gps_gate_url" in temp:
            # load the settings.ini value
            gps_gate_url = clean_string(temp["gps_gate_url"])

        if "gps_gate_port" in temp:
            # load the settings.ini value
            gps_gate_port = parse_integer(temp["gps_gate_port"])

    obj.set_imei(imei)
    obj.set_server_url(gps_gate_url)
    obj.set_server_port(gps_gate_port)

    # we never need these!
    # obj.set_username('Admin')
    # obj.set_password(':Vm78!!')

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
    sock.settimeout(2.0)
    address = (obj.gps_gate_url, obj.gps_gate_port)

    base_app.logger.info(
        "Preparing to connect on TCP socket {}".format(address))

    # attempt to actually lock the resource, which may fail if unavailable
    #   (see CONNECT ERROR note)
    try:
        sock.connect(address)
    except OSError as msg:
        base_app.logger.error("socket.connect() failed - {}".format(msg))

        return -1

    # first test - try our user name:
    # Req = $FRLIN,, Admin,12Ne*14\r\n
    # Rsp = $FRERR,AuthError,Wrong username or password*56\r\n

    # second test - try our IMEI:
    # Req = $FRLIN,IMEI,353547060660845,*47\r\n
    # Rsp = $FRSES,75*7F\r\n

    # expect Req = $FRLIN,IMEI,353547060660845,*47\r\n
    request = obj.get_next_client_2_server()
    base_app.logger.debug("Req({})".format(request))
    sock.send(request)

    # expect Rsp = $FRSES,75*7F\r\n
    try:
        response = sock.recv(1024)
        base_app.logger.debug("Rsp({})".format(response))
        result = obj.parse_message(response)
        if not result:
            base_app.logger.debug("parse result({})".format(result))
    except socket.timeout:
        base_app.logger.error("No response - one was expected!")
        return -1

    # expect Req = $FRVER,1,1,Cradlepoint 1.0*27\r\n
    request = obj.get_next_client_2_server()
    base_app.logger.debug("Req({})".format(request))
    sock.send(request)

    # expect Rsp = $FRVER,1,1,GpsGate Server 3.0.0.2583*3E\r\n
    try:
        response = sock.recv(1024)
        base_app.logger.debug("Rsp({})".format(response))
        result = obj.parse_message(response)
        if not result:
            base_app.logger.debug("parse result({})".format(result))
    except socket.timeout:
        base_app.logger.error("No response - one was expected!")
        return -1

    # expect Req = $FRCMD,,_getupdaterules,Inline*1E
    request = obj.get_next_client_2_server()
    base_app.logger.debug("Req({})".format(request))
    sock.send(request)

    # expect Rsp = $FRRET,User1,_getupdaterules,Nmea,2*07
    try:
        response = sock.recv(1024)
        base_app.logger.debug("Rsp({})".format(response))
        result = obj.parse_message(response)
        if not result:
            base_app.logger.debug("parse result({})".format(result))
    except socket.timeout:
        base_app.logger.error("No response - one was expected!")
        return -1

    # now we LOOP and process the rules!
    while True:
        # expect Rsp = $FRVAL,DistanceFilter,500.0*67
        try:
            response = sock.recv(1024)
            base_app.logger.debug("Rsp({})".format(response))
            result = obj.parse_message(response)
            if not result:
                base_app.logger.debug("parse result({})".format(result))
        except socket.timeout:
            base_app.logger.error("No response - jump from loop!")
            break

    # expect Req = b"$FRWDT,NMEA*78"
    request = obj.get_next_client_2_server()
    base_app.logger.debug("Req({})".format(request))
    sock.send(request)
    # this message has NO response!

    # our fake data, time-fixed to me NOW
    request = fix_time_sentence("$GPGGA,094013.0,4334.784909,N,11612.7664" +
                                "48,W,1,09,0.9,830.6,M,-11.0,M,,*60")
    request = request.encode()
    base_app.logger.debug("Req({})".format(request))
    sock.send(request)

    # this message has NO response!
    time.sleep(2.0)

    sock.close()

    return 0
Esempio n. 7
0
 def set_server_url(self, value):
     """save the GpsGate server URL"""
     value = clean_string(value)
     if self.gps_gate_url != value:
         self.gps_gate_url = value
         self.logger.info("GpsGate: Setting Server URL:{}".format(value))
Esempio n. 8
0
 def set_imei(self, value):
     """make string, remove extra quotes, etc"""
     value = clean_string(value)
     if self.client_imei != value:
         self.client_imei = value
         self.logger.info("GpsGate: Setting IMEI:{}".format(value))
Esempio n. 9
0
 def set_password(self, value):
     """make string, remove extra quotes, etc"""
     value = clean_string(value)
     if self.client_password != value:
         self.client_password = value
         self.logger.info("GpsGate: Setting new PASSWORD:****")
Esempio n. 10
0
def run_router_app(app_base):
    """

    :param CradlepointAppBase app_base: prepared resources: logger, cs_client
    :return:
    """
    # logger.debug("Settings({})".format(sets))

    replay_file_name = DEF_REPLAY_FILE

    try:
        # use Router API to fetch any configured data
        config = GpsConfig(app_base)
        host_ip, host_port = config.get_client_info()
        del config

    except CradlepointRouterOffline:
        host_ip = None
        host_port = 0

    section = "gps"
    if section in app_base.settings:
        # then load dynamic values
        temp = app_base.settings[section]

        # check on our localhost port (not used, but to test)
        if "host_ip" in temp:
            # then OVER-RIDE what the router told us
            app_base.logger.warning("Settings OVER-RIDE router host_ip")
            value = clean_string(temp["host_ip"])
            app_base.logger.warning("was:{} now:{}".format(host_ip, value))
            host_ip = value

        if "host_port" in temp:
            # then OVER-RIDE what the router told us
            app_base.logger.warning("Settings OVER-RIDE router host_port")
            value = parse_integer(temp["host_port"])
            app_base.logger.warning("was:{} now:{}".format(host_port, value))
            host_port = value

        if "replay_file" in temp:
            replay_file_name = clean_string(temp["replay_file"])

    app_base.logger.debug("GPS destination:({}:{})".format(host_ip, host_port))

    if not os.path.isfile(replay_file_name):
        app_base.logger.error(
            "Replay file({}) not found".format(replay_file_name))
        raise FileNotFoundError

    app_base.logger.info("Replay file is named:{}".format(replay_file_name))

    # define the socket resource, including the type (stream == "TCP")
    address = (host_ip, host_port)
    app_base.logger.info("Will connect on {}".format(address))
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    sock.connect(address)

    file_han = None
    start_time = time.time()

    try:
        while True:
            # loop forever
            if file_han is None:
                # reopen the file
                file_han = open(replay_file_name, "r")
                start_time = time.time()

            # this
            line_in = read_in_line(file_han)
            if line_in is None:
                app_base.logger.warning("Close Replay file")
                file_han.close()
                break

            else:
                wait_time = start_time + line_in['offset']
                now = time.time()
                if wait_time > now:
                    delay = wait_time - now
                    app_base.logger.debug("Delay %0.1f sec" % delay)
                    time.sleep(delay)

                nmea_out = gps_nmea.fix_time_sentence(line_in['data']).strip()
                nmea_out += '\r\n'
                app_base.logger.debug("out:{}".format(nmea_out))

                sock.send(nmea_out.encode())

                time.sleep(0.1)

    finally:
        app_base.logger.info("Closing client socket")
        sock.close()

    return 0
Esempio n. 11
0
def run_router_app(app_base):
    """

    :param CradlepointAppBase app_base: prepared resources: logger, cs_client
    :return:
    """
    # logger.debug("Settings({})".format(sets))

    # first, confirm no other function using serial
    value = SerialRedirectorConfig(app_base)
    value.refresh()
    if value.enabled():
        app_base.logger.error("Serial Redirector Function is Active!")
        app_base.logger.error("Aborting SDK application")
        return -3
    app_base.logger.debug("Good: Serial Redirector Function is Disabled.")

    value = SerialGpsConfig(app_base)
    value.refresh()
    if value.enabled():
        app_base.logger.error("Serial GPS Echo Function is Active!")
        app_base.logger.error("Aborting SDK application")
        return -4
    app_base.logger.debug("Good: Serial GPS Function is Disabled.")

    value = SerialGPIOConfig(app_base)
    value.refresh()
    if value.enabled():
        app_base.logger.error("Serial GPIO Function is Active!")
        app_base.logger.error("Aborting SDK application")
        return -5
    app_base.logger.debug("Good: Serial GPIO Function is Disabled.")

    server_loop = ModbusBridge()
    server_loop.logger = app_base.logger

    if "modbus_ip" in app_base.settings:
        temp = app_base.settings["modbus_ip"]
        if "host_ip" in temp:
            # assume is string of correct format
            server_loop.host_ip = clean_string(temp["host_ip"])

        if "host_port" in temp:
            # force to integer
            server_loop.host_port = parse_integer(temp["host_port"])

        if "idle_timeout" in temp:
            # support seconds, or like '5 min'
            duration = TimeDuration(clean_string(temp["idle_timeout"]))
            server_loop.idle_timeout = duration.get_seconds()

        if "protocol" in temp:
            value = validate_ia_protocol(clean_string(temp["protocol"]))
            if value not in (IA_PROTOCOL_MBTCP, IA_PROTOCOL_MBRTU,
                             IA_PROTOCOL_MBASC):
                raise ValueError("Invalid IP-packed Modbus Protocol {}".format(
                    type(value)))
            server_loop.host_protocol = value

    if "modbus_serial" in app_base.settings:
        temp = app_base.settings["modbus_serial"]
        if "port_name" in temp:
            server_loop.serial_name = clean_string(temp["port_name"])

        if "baud_rate" in temp:
            server_loop.serial_baud = parse_integer(temp["baud_rate"])

        if "parity" in temp:
            server_loop.serial_baud = parse_integer(temp["baud_rate"])

        if "protocol" in temp:
            value = validate_ia_protocol(clean_string(temp["protocol"]))
            # confirm is serial, so RTU or ASCII
            if value not in (IA_PROTOCOL_MBRTU, IA_PROTOCOL_MBASC):
                raise ValueError("Invalid Serial Modbus Protocol {}".format(
                    type(value)))
            server_loop.serial_protocol = value

    # this should run forever
    try:
        result = server_loop.run_loop()

    except KeyboardInterrupt:
        result = 0

    return result
Esempio n. 12
0
def run_router_app(app_base):
    """

    :param CradlepointAppBase app_base: prepared resources: logger, cs_client
    :return:
    """
    buffer_size = DEF_BUFFER_SIZE
    replay_file_name = DEF_REPLAY_FILE

    # use Router API to fetch any configured data
    config = GpsConfig(app_base)
    host_ip, host_port = config.get_client_info()
    del config

    section = "gps"
    if section in app_base.settings:
        # then load dynamic values
        temp = app_base.settings[section]

        # check on our localhost port (not used, but to test)
        if "host_ip" in temp:
            # then OVER-RIDE what the router told us
            app_base.logger.warning("Settings OVER-RIDE router host_ip")
            value = clean_string(temp["host_ip"])
            app_base.logger.warning("was:{} now:{}".format(host_ip, value))
            host_ip = value

        if "host_port" in temp:
            # then OVER-RIDE what the router told us
            app_base.logger.warning("Settings OVER-RIDE router host_port")
            value = parse_integer(temp["host_port"])
            app_base.logger.warning("was:{} now:{}".format(host_port, value))
            host_port = value

        if "buffer_size" in temp:
            buffer_size = parse_integer(temp["buffer_size"])

        if "replay_file" in temp:
            replay_file_name = clean_string(temp["replay_file"])

    app_base.logger.debug("GPS source:({}:{})".format(host_ip, host_port))

    # make sure our log file exists & is empty
    file_han = open(replay_file_name, "w")
    file_han.write("[\n")
    file_han.close()

    address = (host_ip, host_port)

    while True:
        # define the socket resource, including the type (stream == "TCP")
        app_base.logger.info("Preparing GPS Listening on {}".format(address))
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        # attempt to actually lock resource, which may fail if unavailable
        #   (see BIND ERROR note)
        try:
            sock.bind(address)
        except OSError as msg:
            app_base.logger.error("socket.bind() failed - {}".format(msg))

            # technically, Python will close when 'sock' goes out of scope,
            # but be disciplined and close it yourself. Python may warning
            # you of unclosed resource, during runtime.
            try:
                sock.close()
            except OSError:
                pass

            # we exit, because if we cannot secure the resource, the errors
            # are likely permanent.
            return -1

        # only allow 1 client at a time
        sock.listen(3)

        while True:
            # loop forever
            start_time = time.time()

            app_base.logger.info("Waiting on TCP socket %d" % host_port)
            client, address = sock.accept()
            app_base.logger.info("Accepted connection from {}".format(address))

            # for cellular, ALWAYS enable TCP Keep Alive (see KEEP ALIVE note)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)

            # set non-blocking so we can do a manual timeout (use of select()
            # is better ... but that's another sample)
            # client.setblocking(0)

            while True:
                app_base.logger.debug("Waiting to receive data")
                data = client.recv(buffer_size)
                # data is type() bytes, to echo we don't need to convert
                # to str to format or return.
                if data:
                    # assume we have multiple sentences per segment recv'd
                    data = data.decode().split()

                    print("data:{}".format(data))

                    file_han = open(replay_file_name, "a")
                    offset = int(time.time() - start_time)

                    for line in data:
                        result = '{"offset":%d, "data":"%s"},\n' % (offset,
                                                                    line)
                        file_han.write(result)

                    app_base.logger.debug("Wrote at offset:{}".format(offset))

                else:
                    break

                time.sleep(1.0)

            app_base.logger.info("Client disconnected")
            client.close()

            # since this server is expected to run on a small embedded system,
            # free up memory ASAP (see MEMORY note)
            del client
            gc.collect()

    return 0