一 Cp56Time2a 介绍
CP56Time2a 是电力系统和工业自动化中广泛使用的时间同步格式,通常用于 IEC 60870-5-101 和 IEC 60870-5-104 协议中。这个格式用于在远程终端单元 (RTU) 和主站 (Master) 之间传输时间信息,保证设备间的时钟同步。
二 CP56Time2a 格式
CP56Time2a 是一个 7 字节(56 比特)的时间戳格式,包含从世纪、年、月、日到毫秒的时间信息,适用于精确时间同步。以下是具体的字段结构:
- 毫秒 (Millisecond) (16 bits): 表示秒的小数部分,以 1 毫秒为单位(0 - 59999),代表一个分钟中的具体时间点。
- 分钟 (Minute) (6 bits): 表示时间中的分钟部分(0 - 59)。最高位 (MSB) 是无效标志位(validity bit),当该位为1时,表示分钟字段无效。
- 小时 (Hour) (5 bits): 表示小时(0 - 23)。
- 天 (Day) (5 bits): 表示当前日期中的天数(1 - 31)。注意:该字段最高位为工作日标志位,表示当前的日期是否为工作日。
- 月 (Month) (4 bits): 表示月份(1 - 12)。
- 年 (Year) (7 bits): 表示年份的后两位(00 - 99),表示的范围为 2000 年到 2099 年。
对应字节码相应位置如下:
- 字节位置0:包含秒值的低位部分。
- 字节位置1:包含秒值的高位部分。
- 字节位置3:分钟
- 字节位置4:小时(24小时制,0-23)
- 字节位置5:天(月份中的号数,1-31)
- 字节位置6:月份(1-12)
- 字节位置7:年份后两位(1-99)
总的来说,Cp56Time2a格式使用了12个字节来表示完整的日期和时间信息,包括秒、毫秒、日、月、以及年份。
三 Cp56Time2a 编码
1 Cp56Time2a 编码与 解码
- 编码:将当前时间转换为 CP56Time2a 格式(7 字节)。
- 解码:从 7 字节的 CP56Time2a 格式还原为标准时间。
用 Java 实现 CP56Time2a 格式的编码和解码的示例代码如下:
import java.nio.ByteBuffer;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Arrays;
public class CP56Time2a {
// 编码当前时间为 CP56Time2a 格式
public static byte[] encodeCP56Time2a(LocalDateTime dateTime) {
ByteBuffer buffer = ByteBuffer.allocate(7);
// 毫秒部分,1分钟 = 60,000毫秒
int millis = (dateTime.getSecond() * 1000) + (dateTime.getNano() / 1000000);
buffer.putShort((short) millis);
// 分钟 (6 bits) + 有效性位 (1 bit)
byte minute = (byte) dateTime.getMinute();
buffer.put(minute);
// 小时 (5 bits)
byte hour = (byte) dateTime.getHour();
buffer.put(hour);
// 天 (5 bits) + 工作日标志 (1 bit)
byte day = (byte) dateTime.getDayOfMonth();
buffer.put(day);
// 月份 (4 bits)
byte month = (byte) dateTime.getMonthValue();
buffer.put(month);
// 年 (7 bits, 两位数年份)
byte year = (byte) (dateTime.getYear() % 100);
buffer.put(year);
return buffer.array();
}
// 解码 CP56Time2a 格式为 LocalDateTime
public static LocalDateTime decodeCP56Time2a(byte[] data) {
ByteBuffer buffer = ByteBuffer.wrap(data);
// 毫秒部分
int millis = buffer.getShort() & 0xFFFF;
int seconds = millis / 1000;
int nanos = (millis % 1000) * 1000000;
// 分钟
int minute = buffer.get() & 0x3F;
// 小时
int hour = buffer.get() & 0x1F;
// 天
int day = buffer.get() & 0x1F;
// 月
int month = buffer.get() & 0x0F;
// 年
int year = 2000 + (buffer.get() & 0x7F);
return LocalDateTime.of(year, month, day, hour, minute, seconds, nanos);
}
public static void main(String[] args) {
// 获取当前时间
LocalDateTime currentTime = LocalDateTime.now(ZoneOffset.UTC);
System.out.println("当前时间: " + currentTime);
// 编码当前时间为CP56Time2a格式
byte[] encodedTime = encodeCP56Time2a(currentTime);
System.out.println("编码后的CP56Time2a格式: " + Arrays.toString(encodedTime));
// 解码回LocalDateTime
LocalDateTime decodedTime = decodeCP56Time2a(encodedTime);
System.out.println("解码后的时间: " + decodedTime);
}
}
代码说明:
- 编码方法 (
encodeCP56Time2a
):将LocalDateTime
转换为 7 字节的 CP56Time2a 格式,包括毫秒、分钟、小时、天、月、年。 - 解码方法 (
decodeCP56Time2a
):将 7 字节的数据转换回LocalDateTime
对象,重新得到时间。
2 Cp56Time2a开发案例
Cp56Time2a编码在开发过程会用到,如以下云快充协议要求就是用Cp56Time2a格式实现传输时间,协议需求开发文档如下图:
用java实现时间格式转换Cp56Time2a编码,如下图:
/**
* @author hua
* @date 2023-09-18 15:33
*/
public class Cp56Time2aUtil {
/**
* 本地时间转Cp56Time2a
* @param date
* @return
*/
public static byte[] toByteCp56Time2a(LocalDateTime date) {
DateTimeFormatter dtf=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println("下发时间:"+dtf.format(date));
byte[] result = new byte[7];
int seconds = date.getSecond();
int milliseconds=seconds*1000;
result[0] = (byte) (milliseconds % 256);
result[1] = (byte) (milliseconds / 256);
result[2] = (byte) date.getMinute();
result[3] = (byte) date.getHour();
result[4] = (byte) date.getDayOfMonth();
result[5] = (byte) date.getMonthValue();
result[6] = (byte) (date.getYear() % 100);
return result;
}
/**
* Cp56Time2a转时间字符串
* @param b Cp56Time2a 数组
* @return
*/
public static String getTimeByCp56Time2a(byte[] b) {
if (b.length == 7) {
String str = "";
int year = b[6] & 0x7F;
int month = b[5] & 0x0F;
int day = b[4] & 0x1F;
int hour = b[3] & 0x1F;
int minute = b[2] & 0x3F;
int s0=b[0]&0xff;
int s1 = ((b[1]&0xff)*256);
int second = s0 + s1;
str += "20" + year + "-"
+ String.format("%02d", month) + "-"
+ String.format("%02d", day) + " "
+ String.format("%02d",hour) + ":"
+ String.format("%02d",minute) + ":"
+ String.format("%02d",second / 1000);
return str + "\n";
} else {
System.out.println("无效格式");
return "";
}
}
public static void main(String[] args) {
//测试1 样例报文时间
// 68(起始标志)12(数据长度)00DF(序列号域)00(加密标志)56(类型)55031412782305(桩编码)98B70E11100314(当前时间:2020-03-16 17:14:47)8A13(帧校验域)
String time="98B70E11100314";
byte[] b=HexTool.hexStringToBytes(time);
System.out.println(time+" 还原示例报文时间 -> "+Cp56Time2aUtil.getTimeByCp56Time2a(b) );
//测试2 当前报文时间
byte[] curTime=Cp56Time2aUtil.toByteCp56Time2a(LocalDateTime.now());
System.out.println(time+" gen cur time hex -> "+HexTool.bytesToHexString(curTime) );
System.out.println("getTimeByCp56Time2a time str -> "+Cp56Time2aUtil.getTimeByCp56Time2a(curTime));
}
}
main方法测试结果如下图:
测试结果正确,在实际开发过程中只需调用以上toByteCp56Time2a方法,把需要的时间按Cp56Time2a格式编码转换byte后直接下发到设备即可。