#  """
#    Copyright (c) 2016- 2024, Wiliot Ltd. All rights reserved.
#
#    Redistribution and use of the Software in source and binary forms, with or without modification,
#     are permitted provided that the following conditions are met:
#
#       1. Redistributions of source code must retain the above copyright notice,
#       this list of conditions and the following disclaimer.
#
#       2. Redistributions in binary form, except as used in conjunction with
#       Wiliot's Pixel in a product or a Software update for such product, must reproduce
#       the above copyright notice, this list of conditions and the following disclaimer in
#       the documentation and/or other materials provided with the distribution.
#
#       3. Neither the name nor logo of Wiliot, nor the names of the Software's contributors,
#       may be used to endorse or promote products or services derived from this Software,
#       without specific prior written permission.
#
#       4. This Software, with or without modification, must only be used in conjunction
#       with Wiliot's Pixel or with Wiliot's cloud service.
#
#       5. If any Software is provided in binary form under this license, you must not
#       do any of the following:
#       (a) modify, adapt, translate, or create a derivative work of the Software; or
#       (b) reverse engineer, decompile, disassemble, decrypt, or otherwise attempt to
#       discover the source code or non-literal aspects (such as the underlying structure,
#       sequence, organization, ideas, or algorithms) of the Software.
#
#       6. If you create a derivative work and/or improvement of any Software, you hereby
#       irrevocably grant each of Wiliot and its corporate affiliates a worldwide, non-exclusive,
#       royalty-free, fully paid-up, perpetual, irrevocable, assignable, sublicensable
#       right and license to reproduce, use, make, have made, import, distribute, sell,
#       offer for sale, create derivative works of, modify, translate, publicly perform
#       and display, and otherwise commercially exploit such derivative works and improvements
#       (as applicable) in conjunction with Wiliot's products and services.
#
#       7. You represent and warrant that you are not a resident of (and will not use the
#       Software in) a country that the U.S. government has embargoed for use of the Software,
#       nor are you named on the U.S. Treasury Department’s list of Specially Designated
#       Nationals or any other applicable trade sanctioning regulations of any jurisdiction.
#       You must not transfer, export, re-export, import, re-import or divert the Software
#       in violation of any export or re-export control laws and regulations (such as the
#       United States' ITAR, EAR, and OFAC regulations), as well as any applicable import
#       and use restrictions, all as then in effect
#
#     THIS SOFTWARE IS PROVIDED BY WILIOT "AS IS" AND "AS AVAILABLE", AND ANY EXPRESS
#     OR IMPLIED WARRANTIES OR CONDITIONS, INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED
#     WARRANTIES OR CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, NONINFRINGEMENT,
#     QUIET POSSESSION, FITNESS FOR A PARTICULAR PURPOSE, AND TITLE, ARE DISCLAIMED.
#     IN NO EVENT SHALL WILIOT, ANY OF ITS CORPORATE AFFILIATES OR LICENSORS, AND/OR
#     ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
#     OR CONSEQUENTIAL DAMAGES, FOR THE COST OF PROCURING SUBSTITUTE GOODS OR SERVICES,
#     FOR ANY LOSS OF USE OR DATA OR BUSINESS INTERRUPTION, AND/OR FOR ANY ECONOMIC LOSS
#     (SUCH AS LOST PROFITS, REVENUE, ANTICIPATED SAVINGS). THE FOREGOING SHALL APPLY:
#     (A) HOWEVER CAUSED AND REGARDLESS OF THE THEORY OR BASIS LIABILITY, WHETHER IN
#     CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE);
#     (B) EVEN IF ANYONE IS ADVISED OF THE POSSIBILITY OF ANY DAMAGES, LOSSES, OR COSTS; AND
#     (C) EVEN IF ANY REMEDY FAILS OF ITS ESSENTIAL PURPOSE.
#  """
import datetime
import logging
import numpy as np
import pandas as pd
import copy
from enum import Enum
from wiliot_core.packet_data.config_files.packet_map import parse_packet, gw_result_dict, construct_packet_from_fields
from wiliot_core.utils.utils import valid_packet_start
from wiliot_core.packet_data.config_files.packet_flow_parameters import get_flow_param


FULL_PACKET_PREFIX = 'full_packet'
LEGACY_PACKET_PREFIX = 'process_packet'


packet_tag_length = 74
MAX_STAT_PARAM_VAL = 65535  # 2 bytes
MAX_GW_CLOCK = 32767  # 7 bits


class InlayTypes(Enum):
    TEO_086 = '086'
    TIKI_096 = '096'
    TIKI_099 = '099'
    BATTERY_107 = '107'
    TIKI_117 = '117'
    TIKI_118 = '118'
    TIKI_121 = '121'
    TIKI_122 = '122'
    TIKI_136 = '136'
    TIKI_H_160 = '160'
    TIKI_160 = '160'
    TIKI_168 = '168'
    TIKI_169 = '169'


class Packet(object):
    """
    Wiliot Packet Object

        :param raw_packet: the raw packet to create a Packet object
        :type raw_packet: str or dict

        :return:
    """

    def __init__(self, raw_packet, time_from_start=None, custom_data=None,
                 inlay_type=None, logger_name=None, ignore_crc=False):
        """
        :param raw_packet:
        :type raw_packet: str or dict
        :param logger_name: the log file we want to write into it.
        :type time_from_start: str
        :param time_from_start: the time when the packet was received according to the gw timer
        :type time_from_start: float
        :param custom_data: the packet custom data as dictionary
        :type custom_data: dict
        :param inlay_type: the antenna/ inlay type (e.g. tiki, teo)
        :type inlay_type: str
        :type ignore_crc: bool
        :param ignore_crc: if True, update for all packets crc_valid = True,
                           relevant for data collected by old GW FW version
        """

        self.is_valid_packet = False
        if logger_name is not None:
            self.logger = logging.getLogger(logger_name)
        else:
            self.logger = None

        # init packet input:
        packet_in = None
        time_in = None
        self.gw_process = False
        if type(raw_packet) is dict and raw_packet['is_valid_tag_packet']:
            packet_in = raw_packet['packet']
            time_in = raw_packet['time_from_start']
            self.gw_process = True
        elif type(raw_packet) is str:
            packet_in = raw_packet
            time_in = time_from_start

        # check packet type:
        if FULL_PACKET_PREFIX in packet_in:
            self.is_full_packet = True
        elif LEGACY_PACKET_PREFIX in packet_in:
            self.is_full_packet = False
        else:
            self.is_full_packet = None

        # parse packet
        try:
            packet_in_strip = valid_packet_start(packet_in) or packet_in
            self.packet_data, self.gw_data = parse_packet(packet_in_strip, self.is_full_packet, ignore_crc)

            # assign results
            self.is_valid_packet = self.gw_data['is_valid_tag_packet'].take(0)
            self.gw_data['time_from_start'] = np.array(time_in)
            self.packet_data['is_full_packet'] = self.is_full_packet
        except Exception as e:
            self.printing_and_logging(f'Could not parse the following packet {packet_in} due to {e}')
            self.is_valid_packet = False
            self.gw_data = {'is_valid_tag_packet': np.array(False)}

        # add more info to packet struct
        self.decoded_data = {}
        if custom_data is not None:
            self.custom_data = custom_data
        else:
            self.custom_data = {}
        if 'timestamp' not in self.custom_data.keys():
            self.custom_data['timestamp'] = datetime.datetime.now().timestamp() * 1000  # ms
        self.inlay_type = inlay_type

        if self.is_valid_packet:
            enc_payload = self.packet_data['enc_payload']
            if len(enc_payload) == 0:
                self.is_valid_packet = False
                self.printing_and_logging(f'the following packet marked as invalid due to '
                                          f'empty tag data payload: {self.packet_data["raw_packet"]}')
                return
            if self.packet_data['enc_payload'] == \
                    self.packet_data['enc_payload'][0] * len(self.packet_data['enc_payload']):
                self.is_valid_packet = False
                self.printing_and_logging(f'the following packet marked as invalid due to identical characters in '
                                          f'the tag data payload: {self.packet_data["raw_packet"]}')
                return
            # clear adi from packet and add it to custom data
            if self.packet_data['ble_type'] != 'LEGACY':
                self.custom_data['adi_per_packet'] = [self.packet_data['adi']]
                self.packet_data['raw_packet'] = construct_packet_from_fields(packet_data=self.packet_data,
                                                                              fields_to_zero=['adi'])

    def __len__(self):
        """
        gets number of sprinkler occurrences in packet
        """
        return self.gw_data['rssi'].size

    def __eq__(self, packet):
        """
        packet comparison
        """
        if self.is_same_sprinkler(packet):
            if len(packet) == self.__len__():
                return all([np.take(packet.gw_data['gw_packet'], i) == np.take(self.gw_data['gw_packet'], i)
                            for i in range(len(packet))])
        return False

    def __str__(self):
        """
        packet print method
        """
        return str(
            'packet_data={packet_data}, gw_data={gw_data}'.format(packet_data=self.packet_data, gw_data=self.gw_data))

    def printing_and_logging(self, message):
        if self.logger:
            self.logger.info(message)
        else:
            print(message)

    def is_packet_from_bridge(self):
        return np.take(self.gw_data['is_packet_from_bridge'], 0)

    def get_payload(self):
        if self.is_valid_packet:
            return self.packet_data['data_uid'] + self.packet_data['raw_group_id'] + self.packet_data['nonce'] + \
                   self.packet_data['enc_uid'] + self.packet_data['mic'] + self.packet_data['enc_payload']
        else:
            return ''

    def get_group_id(self):
        return self.packet_data['group_id']

    def set_inlay_type(self, inlay_type):
        inlay_type = inlay_type if isinstance(inlay_type, str) else inlay_type.value
        self.inlay_type = inlay_type

    def is_in(self, packet):
        """
        is packet contains another packet

        :param packet: the other packet to verify
        :type packet: Packet

        :return: bool
        """
        if self.is_same_sprinkler(packet):
            if np.take(packet.gw_data['gw_packet'], 0) in self.gw_data['gw_packet']:
                return True
        return False

    def get_packet(self):
        """
        gets raw packet string
        """
        return str(self.packet_data['raw_packet'])

    def split_packet(self, index):
        """
        split packet by index
        """
        packet_a = self.copy()
        packet_b = self.copy()
        remain = len(self) - index
        for key in self.gw_data.keys():
            for i in range(index):
                packet_a.gw_data[key] = np.delete(packet_a.gw_data[key], -1)

            for i in range(remain):
                packet_b.gw_data[key] = np.delete(packet_b.gw_data[key], 0)

        return packet_a, packet_b

    def copy(self):
        return copy.deepcopy(self)

    def sort(self):
        """
        sort gw_data lists according to gw_time
        """
        isort = np.argsort(self.gw_data['gw_clock'])
        for key in self.gw_data.keys():
            self.gw_data[key] = self.gw_data[key][isort]

    def get_average_rssi(self):
        return np.average(self.gw_data['rssi'])

    def get_packet_size(self):
        return int(self.packet_data['packet_length'])

    def is_short_packet(self):
        return len(self.get_packet_string(process_packet=False)) < self.get_packet_size()

    def get_packet_content(self, raw_packet=None, get_gw_data=False, sprinkler_num=0):
        if raw_packet is None:
            parsed_packet = self
        else:
            raw_packet = valid_packet_start(raw_packet) or raw_packet
            parsed_packet = Packet(raw_packet=raw_packet)

        if get_gw_data:
            return parsed_packet.packet_data['raw_packet'] + parsed_packet.gw_data['gw_packet'].take(sprinkler_num)

        return parsed_packet.packet_data['raw_packet']

    def get_packet_string(self, i=0, gw_data=True, process_packet=True, full_packet=False):
        """
        gets process_packet string
        """
        if not self.is_valid_packet:
            return None

        process_packet_string = ['', '']
        if process_packet:
            if full_packet:
                raise Exception('Could not get packet string since both full and process packet flags are true')
            process_packet_string = ['process_packet("', '")']
        if full_packet:
            process_packet_string = ['full_packet("', '")']

        if gw_data:
            gw_data = self.gw_data['gw_packet'].take(i)
        else:
            gw_data = ''
        return '{raw_packet}{gw_data}'.format(raw_packet=self.packet_data['raw_packet'],
                                              gw_data=gw_data).join(process_packet_string)

    def get_adva(self):
        return self.packet_data['adv_address']

    def get_flow(self):
        flow_version = hex(int(self.packet_data['flow_ver'], 16))
        return flow_version

    def get_rssi(self):
        return str(self.gw_data['rssi'])

    def get_packet_data_names(self):
        """
        extract all keys name which can potentially be a column in packet df
        :return:
        :rtype: list
        """
        dict_temp = self.__dict__
        keys = list(dict_temp.keys())
        for k in dict_temp.keys():
            if isinstance(dict_temp[k], dict):
                keys = keys + list(dict_temp[k].keys())
        return keys

    def is_same_sprinkler(self, packet):
        raw_packet = packet.packet_data['raw_packet']
        if raw_packet == self.packet_data['raw_packet']:
            return True
        return False

    def append_to_sprinkler(self, packet, output_log=True, do_sort=False):
        status = True
        if self.is_same_sprinkler(packet):
            for i in range(len(packet)):
                try:
                    # stat param should be unique (time + rssi for same packet)- we make sure no duplications are added.
                    if np.take(packet.gw_data['gw_clock'], i) not in self.gw_data['gw_clock'] or \
                            np.take(packet.gw_data['time_from_start'], i) not in self.gw_data['time_from_start']:
                        for key in gw_result_dict.keys():
                            self.gw_data[key] = np.append(self.gw_data[key], np.take(packet.gw_data[key], i))
                        for key in self.custom_data.keys():
                            if key in packet.custom_data.keys():
                                self.custom_data[key] = np.append(self.custom_data[key],
                                                                  np.take(packet.custom_data[key], i))
                            else:
                                self.custom_data[key] = np.append(self.custom_data[key], [None])
                        if do_sort:
                            sort_idx = self.gw_data['time_from_start'].argsort()
                            for k, v in self.gw_data.items():
                                self.gw_data[k] = v[sort_idx]
                            for k, v in self.custom_data.items():
                                self.custom_data[k] = v[sort_idx]
                    else:
                        if output_log:
                            self.printing_and_logging('Tried to add duplicated packet to sprinkler {}'.format(packet))
                except Exception as e:
                    self.printing_and_logging('Failed to add packet {} to sprinkler, exception: {}'
                                              .format(packet, str(e)))
        else:
            self.printing_and_logging('Not from the same sprinkler')
            status = False
        # self.sort()
        return status

    def as_dict(self, sprinkler_index=None):  # None not tested
        packet_data = self.packet_data.copy()
        sprinkler_gw_data = self.gw_data.copy()
        custom_data = self.custom_data.copy()
        if sprinkler_index is not None:
            if sprinkler_index > self.gw_data['stat_param'].size:
                return None
            for gw_attr in gw_result_dict.keys():
                sprinkler_gw_data[gw_attr] = np.take(self.gw_data[gw_attr], sprinkler_index)
            for custom_attr in self.custom_data.keys():
                custom_data[custom_attr] = np.take(self.custom_data[custom_attr], sprinkler_index)
            dict_len = 1
        else:
            dict_len = len(self)
            for k, v in packet_data.items():
                if k == 'raw_packet' and 'adi_per_packet' in custom_data.keys():
                    adi_per_packet_list = custom_data['adi_per_packet']
                    if not isinstance(custom_data['adi_per_packet'], list) and \
                            not isinstance(custom_data['adi_per_packet'], np.ndarray):
                        adi_per_packet_list = [adi_per_packet_list] * dict_len
                    original_raw_packets = [construct_packet_from_fields(
                        packet_data=self.packet_data,
                        fields_to_convert={'adi': adi_per_packet}) for adi_per_packet in adi_per_packet_list]
                    packet_data[k] = original_raw_packets
                    continue
                packet_data[k] = [v] * dict_len

        data = {**packet_data, **sprinkler_gw_data, **custom_data}
        data['gw_process'] = [self.gw_process] * dict_len  # bool should be a list for pd.DataFrame.from_dict(data)
        data['is_valid_packet'] = [self.is_valid_packet] * dict_len  # bool should be a list
        data['inlay_type'] = [self.inlay_type] * dict_len
        return data

    def as_dataframe(self, sprinkler_index=None):
        data = self.as_dict(sprinkler_index=sprinkler_index)
        packet_df = pd.DataFrame.from_dict(data)

        return packet_df

    def get_per(self, expected_sprinkler_count=None):
        """
        Calculates the packet per at the sprinkler
        @param expected_sprinkler_count - The number of packet per sprinkler
        @return packet per at percentage
        """
        if expected_sprinkler_count is None:
            if self.packet_data['test_mode']:
                expected_sprinkler_count = 6
            else:
                expected_sprinkler_count = get_flow_param(flow_in=self.packet_data['flow_ver'],
                                                          param_name='sprinkler_num')
        return 1 - (len(self) / expected_sprinkler_count)

    def get_tbp(self):
        """
        calculates the rate of packets from the same sprinkler
        :return: min_times_found - in msec
        :rtype: int
        """

        def triad_ratio_logic(diff_time_1, diff_time_2, ratio=1.0, error=10):
            """ estimate the time between successive packet according to only 3 packets out of 6 """
            if abs(diff_time_1 - ratio * diff_time_2) <= diff_time_2 / error:
                return True
            elif abs(diff_time_1 - (1 / ratio) * diff_time_2) <= diff_time_1 / error:
                return True
            else:
                return False

        def estimate_diff_packet_time(times_list, pc_time_list):
            if times_list.size < sprinkler_num/2 or times_list.size == 1:
                return None

            # sorting based on the pc time:
            sort_idx = pc_time_list.argsort()
            pc_time_list_sorted = pc_time_list.copy()[sort_idx]
            times_list_sorted = times_list.copy()[sort_idx]

            # fix sorting if several packets have the same timestamp
            fix_sort_idx = sort_idx
            # fix_sort_idx = []
            # ind = 0
            # pc_time_unique, pc_time_counts = np.unique(pc_time_list_sorted, return_counts=True)
            # for unique, counts in zip(pc_time_unique, pc_time_counts):
            #     if counts > 1:  # several packets
            #         ind_with_same_time = [i for i, t in enumerate(pc_time_list_sorted) if t == unique]
            #         fix_sort_idx += list(times_list_sorted[ind_with_same_time].argsort() + ind_with_same_time[0])
            #     else:
            #         fix_sort_idx.append(sort_idx[ind])
            #     ind += counts

            times_list_sorted = times_list_sorted[fix_sort_idx]
            dt = []
            for i, t_hw in enumerate(zip(times_list_sorted[1::], times_list_sorted[:-1])):
                dt_tmp = t_hw[0] - t_hw[1]
                dt_pc = round((pc_time_list_sorted[i + 1] - pc_time_list_sorted[i]) * 1000)
                if dt_pc >= MAX_GW_CLOCK:
                    # HW timing was zeroing during the same packets sprinkler
                    dt_tmp = dt_pc
                elif dt_tmp < 0:
                    dt_tmp += MAX_GW_CLOCK

                dt.append(dt_tmp)

            return dt

        def check_if_nan(lst):
            is_nan = pd.isnull(lst)
            if isinstance(is_nan, bool):
                return is_nan
            if isinstance(is_nan.tolist(), bool):
                return is_nan.tolist()
            if isinstance(is_nan, np.ndarray):
                return any(is_nan)
            return False

        if check_if_nan(self.gw_data['time_from_start']) or check_if_nan(self.gw_data['gw_clock']):
            return None

        if self.packet_data['test_mode']:
            sprinkler_num = 6
        else:
            sprinkler_num = get_flow_param(flow_in=self.packet_data['flow_ver'], param_name='sprinkler_num')
        estimate_diff_time = estimate_diff_packet_time(self.gw_data['gw_clock'], self.gw_data['time_from_start'])
        if estimate_diff_time is None:
            return None
        elif len(self) == sprinkler_num/2 and sprinkler_num == 6:
            if triad_ratio_logic(estimate_diff_time[0], estimate_diff_time[1], ratio=1):
                return None
            else:
                for ratio in [2, 3, 4]:
                    if triad_ratio_logic(estimate_diff_time[0], estimate_diff_time[1], ratio=ratio):
                        estimate_diff_time = [min(estimate_diff_time[0], estimate_diff_time[1]),
                                              max(estimate_diff_time[0], estimate_diff_time[1]) / ratio]
                        break
                if triad_ratio_logic(estimate_diff_time[0], estimate_diff_time[1], ratio=1.5):
                    estimate_diff_time = [min(estimate_diff_time[0], estimate_diff_time[1]) / 2,
                                          max(estimate_diff_time[0], estimate_diff_time[1]) / 3]

        return int(min(estimate_diff_time))

    def extract_packet_data_by_name(self, key):
        """
        extract data from all packet attribute according to the key name
        :param key: the name of the data (the key of the relevant dictionary)
        :type key: str
        :return: list of the data
        :rtype: list
        """

        if key in self.packet_data:
            data_attr = self.packet_data[key]
        elif hasattr(self, 'decoded_data') and key in self.decoded_data:
            data_attr = self.decoded_data[key]
        elif key in self.gw_data:
            data_attr = self.gw_data[key]
        elif key in self.custom_data:
            data_attr = self.custom_data[key]
        else:
            self.printing_and_logging('key:{} does not exist in packet structure')
            return None
        if isinstance(data_attr, np.ndarray):
            data_attr = data_attr.tolist()
        if not isinstance(data_attr, list):
            data_attr = [data_attr]
        return data_attr

    def filter_by_sprinkler_id(self, sprinkler_ids):
        """
        keep only specific sprinklers in a packet according to the sprinkler id
        :param sprinkler_ids:
        :type sprinkler_ids: list
        :return: filtered packet
        :rtype: Packet or DecryptedPacket
        """

        def filter_per_attr(data_attr):
            for key in data_attr.keys():
                if isinstance(data_attr[key], list) or \
                        (isinstance(data_attr[key], np.ndarray) and data_attr[key].size > 1):
                    filtered_data = [data_attr[key][i] for i in sprinkler_ids]
                    if isinstance(data_attr[key], np.ndarray):
                        data_attr[key] = np.array(filtered_data)
                    else:
                        data_attr[key] = filtered_data
            return data_attr

        filtered_packet = self.copy()
        filtered_packet.gw_data = filter_per_attr(filtered_packet.gw_data)
        filtered_packet.custom_data = filter_per_attr(filtered_packet.custom_data)

        return filtered_packet

    def add_custom_data(self, custom_data):
        for key in custom_data.keys():
            if isinstance(custom_data[key], list):
                if len(custom_data[key]) == self.__len__():
                    self.custom_data[key] = custom_data[key]
                else:
                    self.printing_and_logging('add_custom_data failed - '
                                              'the custom data is a list of a different length than'
                                              ' the number of packets')
            else:
                self.custom_data[key] = self.__len__() * [custom_data[key]]


if __name__ == '__main__':

    # test mode packets with different adi, same sprinkler:
    testing_82_tm_adi0 = Packet(
        'full_packet("4731090906D03E34CD8200001EFF000505000060750774415ACDFFFBD069756C550DB1FD75E7B127312C21F3B10A03B1CB12AF2D45AC")')
    testing_82_tm_adi1 = Packet(
        'full_packet("4731090906D03E34CD8200011EFF000505000060750774415ACDFFFBD069756C550DB1FD75E7B127312C21F3B10A03B1CB12AF2D45CE")')
    testing_82_tm_adi2 = Packet(
        'full_packet("4731090906D03E34CD8200031EFF000505000060750774415ACDFFFBD069756C550DB1FD75E7B127312C21F3B10A03B1CB12AF2D4615")')
    testing_82_tm_adi0.append_to_sprinkler(testing_82_tm_adi1)
    testing_82_tm_adi0.append_to_sprinkler(testing_82_tm_adi2)
    print(f'packet len: {len(testing_82_tm_adi0)} with the following adi: '
          f'{testing_82_tm_adi0.custom_data["adi_per_packet"]}')

    packet_1 = {'packet': '03B28DCD99201EFF0005FE0000E0210FFF93B635EBFF1DB118C6D782DC2ED98C404200486436AE8F',
                'is_valid_tag_packet': True, 'adv_address': '03B28DCD9920', 'group_id': 'FE0000', 'rssi': 54,
                'stat_param': 30687,
                'time_from_start': 1.528374, 'counter_tag': 1}
    packet_2 = '03B28DCD99201EFF0005FE0000E0210FFF93B635EBFF1DB118C6D782DC2ED98C404200486436AE82'

    p1 = Packet(packet_1)

    print(p1.get_packet_content('03B28DCD99201EFF0005FE0000E0210FFF93B635EBFF1DB118C6D782DC2ED98C404200486436AE82', get_gw_data=True),)

    p2 = Packet(packet_2)

    p3 = Packet('0437E62F33341E16AFFD02000058AD2AE76DE1D314B003083386A2A1AD139E42C10A0448F93472DC')
    print(p3.get_payload())

    packet_no_gw = Packet('03B28DCD99201EFF0005FE0000E0210FFF93B635EBFF1DB118C6D782DC2ED98C4042004864')
    packet_corrupted_length = Packet('03B28DCD99201EFF0005FE0000E0210FFF93B635EBFF1DB118C6D782DC2ED98C4042004864x')

    print(p1.get_packet_string(0))
    print(p1.get_average_rssi())

    print(p1 == p2)
    print(p1.append_to_sprinkler(p2))

    p6_1 = Packet(raw_packet='473109090673E063E00800002616AFFDFE0080FB7140E0F9B527E8E4751A0A18A99E360281421735AF8A000281415D687CC27F529230',
                  time_from_start=81.092617)
    p6_2 = Packet(raw_packet='473109090673E063E00800002616AFFDFE0080FB7140E0F9B527E8E4751A0A18A99E360281421735AF8A000281415D687CC27F52DEA2',
                  time_from_start=228.187184)
    p6 = p6_1.append_to_sprinkler(p6_2)
    tbp = p6_1.get_tbp()

    p6_3 = Packet(raw_packet='473109090695564AAB0800002616AFFDFE00C05AA9955A56FF3B552A24B28D275BDC8647CFDA0750ABA8541FF99340948F41D450AFCC',
                  time_from_start=38.627151)
    p6_4 = Packet(
        raw_packet='473109090695564AAB0800002616AFFDFE00C05AA9955A56FF3B552A24B28D275BDC8647CFDA0750ABA8541FF99340948F41D451BB8E',
        time_from_start=105.55923)
    p6a = p6_3.append_to_sprinkler(p6_4)
    tbp2 = p6_3.get_tbp()

    p6_5 = Packet(
        raw_packet='473109090695564AAB0800002616AFFDFE00C05AA9955A56FF3B552A24B28D275BDC8647CFDA0750ABA8541FF99340948F41D450FFFC',
        time_from_start=0.0)
    p6_6 = Packet(
        raw_packet='473109090695564AAB0800002616AFFDFE00C05AA9955A56FF3B552A24B28D275BDC8647CFDA0750ABA8541FF99340948F41D4510010',
        time_from_start=10.55923)
    p6b = p6_5.append_to_sprinkler(p6_6)
    tbp3 = p6_5.get_tbp()

    p6_7 = Packet(
        raw_packet='06455AD22E061E16AFFDFE0040FFBD941223357780F3FBC45BA7E13D7E6AFB4F89D61463EA3EFF9A',
        time_from_start=55796.199652)
    p6_8 = Packet(
        raw_packet='06455AD22E061E16AFFDFE0040FFBD941223357780F3FBC45BA7E13D7E6AFB4F89D61463EA3E00DA',
        time_from_start=55796.199652)
    p67 = p6_7.append_to_sprinkler(p6_8)
    tbp4 = p6_7.get_tbp()

    print(len(p1))
    print(p1.get_average_rssi())

    p1_dict = p1.as_dict()
    p1_df = pd.DataFrame(data=p1_dict)
    print(p1_df)

    print('end')
