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

时间:2016-03-20 11:22:39

标签: python datetime timestamp dnp3

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

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

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

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

如果有人能帮助我完成这些步骤,我们将非常感激!

2 个答案:

答案 0 :(得分:3)

您可以通过简单地组合字节来创建一个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()方法进一步格式化此datetime对象。实现这一切的代码是

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可打印字符),但它们是等效的。

答案 1 :(得分:1)

从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