将 48 位(6 个八位字节)从 DNP3 时间转换为 python 中的时间戳

Convert 48-bits (6 octets) from DNP3 time to timestamp in python

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

根据DNP3标准,"DNP3 time (in the form of an UINT48): Absolute time value expressed as the number of milliseconds since the start of January 1, 1970"。

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

# 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)

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

您可以简单地组合字节以创建一个 48 位整数和一些 bitwise operations。您可以使用 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) 将 return \x00\x00\x00\x00\x00\x00datetime(1970, 1, 1, 0, 0, 0, 1000)(1970 纪元后一毫秒)将 return \x01\x00\x00\x00\x00\x00.

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

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

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

将整数解释为"milliseconds since epoch":

>>> 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 版本,将小端顺序的字节转换为无符号整数:

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