【问题标题】:Convert 48-bits (6 octets) from DNP3 time to timestamp in python将 48 位(6 个八位字节)从 DNP3 时间转换为 python 中的时间戳
【发布时间】:2016-07-06 21:38:15
【问题描述】:

我正在尝试使用 python 将 48 位(8 个八位字节)转换为时间戳,用于一个小安全项目。我正在处理来自 DNP3 协议的一些网络数据包,并且我正在尝试解码每个 DNP3 类对象的时间戳值。

根据 DNP3 标准,“DNP3 时间(以 UINT48 的形式):表示为自 1970 年 1 月 1 日开始的毫秒数的绝对时间值”。

我有以下八位字节需要转换为日期时间:

# List of DNP3 timestamps
DNP3_ts = []

# Feb 20, 2016 00:27:07.628000000 UTC
DNP3_ts.append('\xec\x58\xed\xf9\x52\x01')

# Feb 20, 2016 00:34:08.107000000 UTC
DNP3_ts.append('\x6b\xc3\xf3\xf9\x52\x01')

# Feb 20, 2016 00:42:40.460000000 UTC
DNP3_ts.append('\xcc\x94\xfb\xf9\x52\x01')

# Feb 20, 2016 00:56:47.642000000 UTC
DNP3_ts.append('\x1a\x82\x08\xfa\x52\x01')

# Feb 20, 2016 00:56:48.295000000 UTC
DNP3_ts.append('\xa7\x84\x08\xfa\x52\x01')

# Feb 20, 2016 00:58:21.036000000 UTC
DNP3_ts.append('\xec\xee\x09\xfa\x52\x01')

# Feb 20, 2016 01:17:09.147000000 UTC
DNP3_ts.append('\x9b\x25\x1b\xfa\x52\x01')

# Feb 20, 2016 01:49:05.895000000 UTC
DNP3_ts.append('\xe7\x64\x38\xfa\x52\x01')

# Feb 20, 2016 01:58:30.648000000 UTC
DNP3_ts.append('\xf8\x02\x41\xfa\x52\x01')

for ts in DNP3_ts:
    print [ts]

所以我需要弄清楚以下步骤:

# 1. Converting the octets into a 48bit Integer (which can't be done in python)

# 2. Using datetime to calculate time from 01/01/1970

# 3. Convert current time to 48bits (6 octets)

如果有人可以帮助我完成这些步骤,将不胜感激!

【问题讨论】:

    标签: python datetime timestamp dnp3


    【解决方案1】:

    您可以简单地组合字节以创建一个带有一些bitwise operations 的 48 位整数。您可以使用 ord() 将每个八位位组转换为 uint8,然后将它们左移 8 的不同倍数,以便它们都占据 48 位数字中的不同位置。

    DNP3 encodes the bytes in a reverse order。为了形象化这一点,让你的八位位组从左到右称为 A-F,A 的位称为 aaaaaaaa,等等。所以从你的八位位组到你想要实现的 48 位数字的顺序。

           A        B        C        D        E        F
    ffffffff eeeeeeee dddddddd cccccccc bbbbbbbb aaaaaaaa
    

    获得毫秒数后,将它们除以 1000 以获得以秒为单位的浮点数,并将其传递给 datetime.datetime.utcfromtimestamp()。您可以使用strftime() 方法进一步格式化此日期时间对象。实现这一切的代码是

    from datetime import datetime
    
    def dnp3_to_datetime(octets):
        milliseconds = 0
        for i, value in enumerate(octets):
            milliseconds = milliseconds | (ord(value) << (i*8))
    
        date = datetime.utcfromtimestamp(milliseconds/1000.)
        return date.strftime('%b %d, %Y %H:%M:%S.%f UTC')
    

    通过为您的每一次 DNP3 调用此函数,您将获得以下结果。

    Feb 19, 2016 14:27:07.628000 UTC
    Feb 19, 2016 14:34:08.107000 UTC
    Feb 19, 2016 14:42:40.460000 UTC
    Feb 19, 2016 14:56:47.642000 UTC
    Feb 19, 2016 14:56:48.295000 UTC
    Feb 19, 2016 14:58:21.036000 UTC
    Feb 19, 2016 15:17:09.147000 UTC
    Feb 19, 2016 15:49:05.895000 UTC
    Feb 19, 2016 15:58:30.648000 UTC
    

    您会注意到这些结果正好滞后 8 小时。我无法弄清楚这种差异,但我认为我的方法没有错。

    要从日期时间转到 DNP3 时间,请从 converting the time to a timestamp of milliseconds 开始。然后,通过一次右移和屏蔽 8 位,您可以构造 DNP3 八位字节。

    def datetime_to_dnp3(date=None):
        if date is None:
            date = datetime.utcnow()
        seconds = (date - datetime(1970, 1, 1)).total_seconds()
        milliseconds = int(seconds * 1000)
        return ''.join(chr((milliseconds >> (i*8)) & 0xff) for i in xrange(6))
    

    如果您不带参数调用它,它会为您提供当前时间,但您可以选择指定任何特定的日期时间。例如,datetime(1970, 1, 1) 将返回 \x00\x00\x00\x00\x00\x00datetime(1970, 1, 1, 0, 0, 0, 1000)(1970 纪元后一毫秒)将返回 \x01\x00\x00\x00\x00\x00

    注意,根据 DNP3 时间中的字节,如果您尝试打印它们,可能会得到奇怪的符号。不过不用担心,字节仍然存在,只是 Python 试图将它们编码为字符。如果您想查看相互干扰的各个字节,只需打印list(DNP3_ts[i])。您可能会注意到它将'\x52' 打印为R(类似于许多ASCII 可打印字符),但它们是等价的。

    【讨论】:

    • 你是个传奇!非常感谢您的详细解释!时间戳来自澳大利亚布里斯班 (UTC +10)。我认为设备的时钟可能配置错误。这里显示的是从布里斯班时区到 UTC 0 的转换,时间与我之前提供的时间 World Clock converter 相符。干杯!
    • @nicRodz 这太棒了。我几乎可以肯定这与我可以获得纪元日期时间的正确结果这一事实有一定的偏差。
    【解决方案2】:

    在 Python 3 中从输入字节中获取整数:

    >>> millis = int.from_bytes(b'\xec\x58\xed\xf9\x52\x01', 'little')
    >>> millis
    1455892027628
    

    将整数解释为“自纪元以来的毫秒数”:

    >>> from datetime import datetime, timedelta
    >>> utc_time = datetime(1970, 1, 1) + timedelta(milliseconds=millis)
    >>> str(utc_time)
    '2016-02-19 14:27:07.628000'
    

    注意:结果与您的问题 (Feb 20, 2016 00:27:07.628) 的评论中提供的结果不同。

    如果您需要支持较旧的 Python 版本,以 little-endian 顺序将字节转换为无符号整数:

    >>> import binascii
    >>> int(binascii.hexlify(b'\xec\x58\xed\xf9\x52\x01'[::-1]), 16)
    1455892027628
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-05-04
      • 2018-05-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-28
      相关资源
      最近更新 更多