简介:《电力需求响应信息模型 第1部分:集中式空调系统》对应T/CEC 239.1—2019标准,面向从事需求响应终端、接口设计与开发的电力科研人员、设备厂商及智能电网集成开发者,用于解决集中式空调参与负荷调度时信息建模不统一、互操作困难等问题。资源包仅含1个PDF文件,约396KB,正文围绕建模原则、数据类型、通用包与专用包展开,并附资料性附录给出需求响应能力计算方法;其中建模原则强调实用性、便捷性、适用性与扩展性,数据类型覆盖整型、字符串、浮点、布尔量及日期时间等,便于读者直接对照标准梳理终端数据字段与接口结构。已有307人学习,适合需要引用标准条款、搭建空调负荷响应模型或核对通信数据定义的技术人员查阅。
1. 一套模型标准,能省掉空调需求响应联调里的两周返工
给一栋几万平方米的商业综合体做空调负荷调控,最费时间的往往不是控制策略,而是双方对「一度电到底该传什么」达不成一致。电力需求响应信息模型这套东西,把集中式空调系统在需求响应场景下要说的话固定成了十来个信息类和一批带单位的属性:主机额定功率怎么描述、冷却水进出温差挂在哪、可削减容量用什么类型表达,都有对应字段。这份第 1 部分专门针对集中式空调系统,正文加附录二十来页,管的是自动需求响应终端和接口之间数据长什么样,不管控制算法怎么跑。适合三类人:写终端固件的、做平台侧解析的、以及要给甲方解释「为什么这套东西不能随手加字段」的集成方。
2. 数据类型:每个物理量都要裹三层,六个基本型各有各的坑
2.1 value + unit + multiplier:为什么不能直接传一个 3.5
先看一个最容易翻车的地方。标准里的物理量类型不是裸 Float,而是统一由value、unit、multiplier三个属性组成。unit是常量,比如有功功率类型ActivePower的unit=W(const),multiplier取UnitMultiplier枚举,表示数量级。这意味着 800 kW 的主机,合法写法是{"value": 800.0, "unit": "W", "multiplier": "k"},也可以写成{"value": 800000.0, "unit": "W", "multiplier": null},但不该出现{"value": 800.0, "unit": "kW"}这种把倍数塞进单位的写法。
这套设计的好处很直接:跨品牌对接时,谁都不用猜对面用的是瓦还是千瓦。代价是解析端必须把三个字段一起读,只取value的代码在遇到multiplier非空时会把 800 kW 读成 800 W,差三个数量级。我见过最典型的一次翻车是负荷聚合商平台把削减量放大了一千倍报上去,调度侧直接判定数据异常踢回,排查了半天才发现是反序列化时把multiplier字段丢了。
还要注意m、c、d、k、M、G这六个乘数字面量里,m和UnitSymbol里立方米的m3、米每秒的m/s长得像,但分属两个字段,解析时只能按字段位置判断,不能靠字符串前缀去猜。字符串类String的编码标准里写死了 UTF-8,长度不限,这意味着设备名称里出现中文、括号、全角字符都是允许的,数据库设计时别按 ASCII 去卡长度。
2.2 十七个物理量类型与单位枚举的对照
标准正文第 5 章列了从电压到转速的一整套类型,每一个都遵循上面那套三属性结构,区别只在unit常量不同。把常用的一部分列出来,方便做字段映射表:
| 类型名 | unit 常量 | 对应 UnitSymbol 枚举 | 常见 multiplier 用法 |
|---|---|---|---|
| Voltage | V | V | k(10 kV 进线) |
| CurrentFlow | A | A | 空或 k |
| Frequency | Hz | Hz | 空 |
| ApparentPower | VA | VA | k |
| ActivePower | W | W | k(机组铭牌功率) |
| ReactivePower | var | var | k |
| RealEnergy | Wh | Wh | k 或 M |
| Temperature | degC | T | 空 |
| SpecificHeatCapacity | J/kg*K | J/kg*K | 空 |
| ThermalResistance | ℃/W | ℃/W | 空 |
| Pressure | Pa | Pa | k |
| Volume | m³ | m3 | 空 |
| Density | kg/m³ | kg/m3 | 空 |
| FlowRate | m3/s | m3/s | 空 |
| WindSpeed | m/s | m/s | 空 |
| Illumination | Lx | Lx | 空 |
| CalorificValue | J/kg | J/kg | 空 |
| RotationSpeed | rpm | rpm | 空 |
温度那一行是必须单独拎出来说的:Temperature类型定义里写的是unit=degC(const),但UnitSymbol枚举表里的字面量是T。两处表述不一致,落地时就出现两种实现,有人传"degC",有人传"T"。这种同一标准内部的口径分叉,光靠读文档看不出来,只有两边对着跑一次数据才会暴露。稳妥做法是在自己的适配层建一张别名表,把T和degC都归一到同一个内部标识,出口统一写T。
还有一类容易被忽略:RotationSpeed的类型属性名在表 18 里写成了首字母大写的Value,其余类型都是小写value。用强类型语言生成代码时如果严格按表来,会得到一个和基类对不上的属性名。这类问题在手工写解析器时无所谓,在自动生成代码的场景里就是编译不过。
2.3 DateTime 与 Duration:两个最容易被 JSON 序列化毁掉的类型
六个基本型里,Integer、String、Float、Boolean都没什么争议,麻烦集中在时间相关的两个。
DateTime规定形如yyyy-mm-ddThh:mm:ss.sss,遵从 ISO 8601,UTC 时区用尾部Z指明,本地时区相关的表示写成yyyy-mm-ddThh:mm:ss.sssZ±hh:mm。这个写法里Z和±hh:mm是同时出现的,而绝大多数语言的日期时间格式化器都不接受这种组合,Java 的DateTimeFormatter.ISO_OFFSET_DATE_TIME会直接抛解析异常,Python 的datetime.fromisoformat在旧版本上也会卡住。
落地时的处理办法只有一条:内部统一转成 UTC 带Z的形式存和传,本地偏移在展示层算,不要把两种表示混在同一条链路里。这样跨时区部署的聚合平台才不会在零点切换时把「今天」的响应事件算到昨天去。毫秒位固定三位,哪怕值是 0 也要补.000,否则严格的校验器会拒绝。
Duration规定形如PnYnMnDTnHnMnS,字母 T 分隔日期段和时间段,只有秒可以带小数,其余必须是整数。需求响应里最常用的几个值是PT5M、PT15M、PT30M、PT1H。出问题的地方在于很多人按日常习惯写90min、1.5h、PT1.5H,这三种写法没有一个是合法的。PT1.5H尤其容易中招,因为它看起来很像 ISO 8601,但规范里小数只能出现在秒位上,正确写法是PT1H30M。
给一段转换代码,把内部使用的秒数转成标准 Duration 字符串,避免手拼出错:
def seconds_to_duration(total_seconds: int) -> str: """把秒数转成 ISO 8601 Duration,只输出时/分/秒,够需求响应场景用""" if total_seconds <= 0: raise ValueError("duration must be positive") hours, rem = divmod(total_seconds, 3600) minutes, seconds = divmod(rem, 60) out = "PT" if hours: out += f"{hours}H" if minutes: out += f"{minutes}M" if seconds: out += f"{seconds}S" # 整小时的情况也要保证至少有一个字段,否则回退成 PT0S 是不合法的 return out if out != "PT" else "PT0S"这段函数的关键点是两个边界:输入为 0 或负数时直接抛异常,因为持续时间为零的响应事件在业务上没有意义;divmod拆出来的分和秒为 0 时不能输出PT0M0S这类冗余字段,部分校验器会判为不规范。参数上只处理时分秒,年月日段在需求响应场景里用不到,硬加反而增加解析歧义。
2.4 九个枚举型的扩展位怎么留
标准里除了UnitSymbol和UnitMultiplier,还有七个业务枚举:用户类型、通信方式、需求响应协议、底层通信协议、需求响应项目类型、控制方式、故障状态。这七个枚举有一个共同设计:最后一项都是x-{User-defined},允许厂商扩展。
扩展的写法是x-前缀加自定义标识,比如某厂商的私有协议可以写成x-vendorA-v1。要注意的是标准值不能被扩展值覆盖,也就是说不能自己造一个DLC2去替代已有的DLC。解析端的正确姿势是:遇到x-开头的值不抛异常,降级成「未知但可用」的枚举,同时记录原始字符串;遇到既不是标准值也不是x-前缀的,才判定为非法报文。
用户类型枚举里的三个标准值是industry、business、household,对应工业、商业、居民。集中式空调系统的用户基本落在business或industry上,但别想当然按建筑性质硬映射,一个工业园区里的办公楼到底算哪个,得看供电方给这个户号定的分类,最终以UserNum对应的档案为准。
3. 通用包:四张表撑起设备台账、用户档案、自动化能力和合约
3.1 EQProductInfo 与 EQUserInfo:必填与可选的边界
通用包一共四个信息类,前两个管「这台设备是谁的、哪来的」。
EQProductInfo有七个属性,其中EqName、EqID、SupplierName、DeviceModel是必填,BrandName、ProductNum、ProductDate标注为可选项。这里有个实操层面的坑:EqID要求是设备 ID,但标准没有规定编码规则,不同厂商各写各的,最后平台侧收到一堆重名的01、02。我一般的做法是把SupplierName + DeviceModel + ProductNum拼成内部唯一键,EqID保持原样透传,只做展示不做索引。
EQUserInfo里UserNum的约束写得很明确:要与供电方分配给用户的用电客户编号一致,MeterNum是用户关口电表的表号。这两个字段是后续所有结算和核验的锚点,千万不能自己编号。longitude、latitude是可选的经纬度,StartDate也是可选。经纬度这块标准没规定坐标系,做地理分布展示时两边得先约好,否则同一栋楼在两个系统里能差出几百米。
UserIndustry是自由字符串而不是枚举,这一点常被误用。它不是让你填「商业」两个字的,规范的做法是填行业分类代码或者标准行业名称,具体口径跟聚合平台约定。填得太随意,后期按行业做负荷画像时就没法聚合。
3.2 EQAutoAbilityInfo:三层协议别写混
这个类只有七个属性,但承载的信息密度很高,也是最容易填错的一个。ComAbility是布尔量,设备能不能对外通信;ComMode是通信方式,枚举值包括本地人机界面、本地通信端口、本地无线、本地有线、远程无线;DRProtocol是设备直接参与需求响应时与远程主站交互的协议,标准值有行业信息交换规范、两种开放协议版本以及扩展位;UnderlyingProtocol是底层承载协议,标准值是以太网、两种现场总线和扩展位。
三层协议一定要分清对象:DRProtocol管的是「和需求响应主站说什么话」,UnderlyingProtocol管的是「这些话跑在什么线上」。把楼宇自控协议填进DRProtocol是最常见的错误,这类协议是设备与本地网关之间的语言,不属于需求响应业务层。反过来,把信息交换规范填进UnderlyingProtocol也是错的。
后面三个布尔量更微妙。On-OffAbility表示支持刚性控制,也就是能接受启停指令;Flexability表示支持柔性调节,能接受功率或温度设定值调整。注意这个属性名的拼写就是Flexability,标准原文如此,不是常见的Flexibility。用强类型语言做序列化时如果自作主张改成正确拼写,对面按标准字段名取值会永远取到空。ThirdPartyPlatform表示设备已经接了第三方平台,填 true 意味着后续下发指令要考虑指令冲突和状态回读延迟。
ComMode里的RemoteWireless描述举例就是 4G,但LocalWireless描述里也提到了无线连接,区别在于连的是本地网关还是公网。分不清这两个,会导致终端在离线缓存和直连上报之间选错策略。
3.3 UserDRContract:次数约束与 [1..1] 到 [0..n] 关联
合约类只有两个属性:MaxTimes是理想状态下设备每年参与需求响应的最大次数,ValidTimes是已经参与的次数,都是整型。看上去简单,实际是争议最多的地方——这两个计数的维护方必须在接口对接前定死。主站记一套、终端记一套、两边都自增,年底对账必然对不上。可行做法是终端只上报事件完成情况,次数由主站侧统一累加后回写。
关联关系在表 32 里:起始端[1..1]是合约,目标端[0..n]是需求响应项目,类型取DRProgrammeType枚举,涵盖分时电价、尖峰电价、实时电价、可中断响应、直接负荷控制、需求侧竞价六种。一个合约可以挂多个项目类型,这个0..n的含义是设备可能只参与其中一两项,解析时不能假设数组非空。
把四个通用包串起来看,一台设备从出厂到签约的完整快照大概是这样(下面是内部联调用的 JSON 映射,标准本身不规定具体编码格式):
{ "EQProductInfo": { "EqName": "离心式冷水机组", "EqID": "AC-MAIN-01", "SupplierName": "某设备厂商", "DeviceModel": "LX-800", "ProductDate": "2023-06-15T00:00:00.000Z" }, "EQUserInfo": { "UserName": "某商业综合体", "UserType": "business", "UserIndustry": "商业建筑", "UserNum": "U0000000001", "MeterNum": "M0000001" }, "EQAutoAbilityInfo": { "ComAbility": true, "ComMode": "LocalCommport", "DRProtocol": "DL/T 1867-2018", "UnderlyingProtocol": "BACnet", "On-OffAbility": true, "Flexability": true, "ThirdPartyPlatform": true }, "UserDRContract": { "MaxTimes": 30, "ValidTimes": 7, "DRProgramme": ["DLC", "IL"] } }这份快照里有三处值得对着看:ProductDate用了 UTC 带 Z 的写法,且补齐了三位毫秒;ComMode选本地通信端口,因为设备是 RS485 接网关而不是直连公网;DRProgramme数组里挂了两个项目类型,说明这台机组既接受直接负荷控制也接受可中断响应,但ValidTimes是 7,意味着今年已经用掉七次,后面下发指令前要先看剩余额度。
4. 专用包:把一套中央空调拆成十个类
4.1 ACType 与 EqType:系统类型和部件类型是两码事
专用包开篇四个枚举,前两个是系统级和工作模式级的,后两个是部件级的。
WorkModeType只有制冷和制热两个标准值,加扩展位。看着简单,实际映射时会遇到「过渡季通风」这种既非制冷也非制热的状态,标准里没有对应字面量,只能走扩展或者归到停机。我倾向于走扩展,因为归到停机后,负荷预测模型会把通风工况的功率当成零,误差会累积。
OperationStatusType有停机、运行、待机、故障四个值。待机和停机的区别在于有没有上电待命,做可调容量评估时这两者的可用潜力完全不同,别合并。
ACType描述集中式空调的类型:水冷、风冷、多联机。EqType描述系统内部组成部件的类型,标准值只有主机机组、冷热水泵、冷却水泵三类。这里有个明显的建模缺口:冷却塔风机、末端风机在ACComponentRatedParameter的Num属性描述里被提到了,但EqType枚举里没有对应的字面量。也就是说,如果要给冷却塔风机建额定参数,只能走x-扩展。这一点在项目立项评估阶段就该确认,否则建模做到一半才发现对不上,返工成本很高。
另外Chilled(Hot)WaterPump这个字面量带圆括号,在多数编程语言里不能直接当枚举标识符用,代码生成工具通常会做转义或者加前缀。手工写映射表时记得留一条对照,别把括号写成全角。
4.2 额定参数分两层:整机一层,部件一层
RatedParameter描述整机层面的额定参数:系统类型、室内制冷采暖面积(平方米)、室内平均高度(米)、制热工况能效比、制冷工况能效比、所在建筑物能量损失系数(单位是千焦每分钟)。这六个量是算需求响应能力的基础输入,附录里的公式全靠它们。
ACComponentRatedParameter描述部件层面:部件类型、数量、额定电流、额定电压、额定运行功率、额定频率,外加一个可选的负荷率限定值。注意额定功率的类型是有功功率,基础单位是瓦,但属性描述里写的是千瓦。规范读法是value存瓦、multiplier填k,不能改unit。
两层参数之间要能自洽。一个自检方法是:把各部件额定功率乘数量求和,再跟整机容量和能效比对一遍。如果一台标称 800 kW 冷量的机组,主机加水泵加风机的额定功率加起来超过冷量的一半,那多半是某一层的单位写错了,或者把备用机组重复计入了。
Num属性描述里明确提到了备用组成部件,但是没有单独的「是否备用」字段。落地时只能在EqType上用扩展区分,或者在数量里只算主用,备用单独建一条记录。两种做法各有代价,前者污染枚举,后者增加记录数,选一个并写进接口文档。
4.3 实时量与温湿度量:采样对齐比字段名更重要
RealTimeInfo有六个属性:实时单相或三相电压、实时电流、实时频率、实时总有功功率、实时总无功功率,以及可选的主机负载率。TRHParameter有十一个属性,室内设定温度、室内平均温度、室内平均相对湿度、室外平均温度、室外平均相对湿度,加上冷凝器进出口温度、蒸发器进出口温度、采暖水送回水温度,其中冷凝器那两个是可选的。
字段本身不难,难在两个类都没有时间戳属性。也就是说,标准定义了「传什么值」,但没定义「这个值是什么时候的」。电量和温度是不同频率采样的,实时量可能十几秒一次,温湿度可能一分钟一次,没有时间戳就只能靠报文到达顺序推,网络抖动一乱,功率曲线和温度曲线就对不齐,算出来的可调容量会有明显偏差。
常见做法是在外层封装上加一个统一的采集时间戳,并约定所有子类的属性都以这个时刻为准;或者走扩展,给每个类加x-timestamp属性。前一种做法改动小,但要求终端和主站在采集周期上严格同步;后一种更精确,代价是扩展字段跨厂商兼容性差。我在项目里一般选前者,同时在报文里带上采集周期,让主站知道这批数据的最大陈旧度。
电压电流的相别问题同样没有标准字段。三相设备传一个总的有功功率没问题,传电压电流就必须说清是单相还是三相、具体哪一相。可用的办法是按相拆成多条实例,在实例标识里带相别;或者走扩展加相别属性。这个约定必须在接口文档第一页写清楚。
4.4 DRAbility:三元组不能拆开传
DRAbility只有三个属性:可增加的有功功率、可削减的有功功率、参与需求响应的持续时间。前两个是有功功率类型,第三个是持续时间类型。
这三个值是一个整体。单传一个「可削减 138 kW」没有意义,因为它必然对应某个持续时间,削减 138 kW 撑 1 小时和撑 15 分钟是完全不同的两件事。附录 A 的公式正是用来算这一组的:给定持续时间,反算最大可削减功率;给定功率,反算能撑多久。
下发和上报时都要带着这组值一起走,主站侧的邀约通常也按「功率 + 时长」成对给。如果业务上确实需要表达「不同时长下的不同能力」,那就要发多条DRAbility实例,构成一条阶梯曲线。
下面是一段专用包的快照,重点是看RatedPower的 multiplier 怎么写、DRAbility三个字段怎么配:
{ "RatedParameter": { "ACTypes": "WaterCool", "RoomArea": 20000.0, "RoomHeight": 3.5, "COP": 3.2, "EER": 3.0, "LossFactor": 120.0 }, "ACComponentRatedParameter": [ { "EqType": "MainEngine", "Num": 2, "RatedVoltage": {"value": 10.0, "unit": "V", "multiplier": "k"}, "RatedCurrent": {"value": 52.0, "unit": "A", "multiplier": null}, "RatedPower": {"value": 800.0, "unit": "W", "multiplier": "k"}, "RatedFrequency": {"value": 50.0, "unit": "Hz", "multiplier": null} }, { "EqType": "Chilled(Hot)WaterPump", "Num": 3, "RatedPower": {"value": 45.0, "unit": "W", "multiplier": "k"} } ], "DRAbility": { "IncreasedPower": {"value": 60.0, "unit": "W", "multiplier": "k"}, "DecreasedPower": {"value": 138.0, "unit": "W", "multiplier": "k"}, "TimeDuration": "PT1H" } }这段数据里,主机 2 台各 800 kW,冷冻水泵 3 台各 45 kW,unit恒为W,数量级靠multiplier: "k"表达。DRAbility里削减 138 kW、持续PT1H,这个数正好是下一章公式按 2 万平米、层高 3.5 米、温差 2 K、能效比 3.0 算出来的结果。
5. 避坑与排查:五类现场最容易撞上的模型错
5.1 五条踩坑记录
功率差一千倍。现象:终端上报主机额定功率 800,平台显示 800 W,容量评估结果小得离谱。原因:RatedPower的类型是有功功率,基础单位是瓦,但属性描述写的是千瓦,一端按描述实现、另一端按类型实现。解决:全链路统一按value存瓦、multiplier表达数量级,接口文档里把这条写成红线,联调第一轮就用一条 800 kW 的样例互验。
温度解析失败。现象:含温度字段的报文被对方直接丢弃,日志里报非法枚举值。原因:Temperature类型定义写degC,UnitSymbol枚举字面量写T,两边各取一边。解决:适配层建别名表,T和degC都映射到内部统一标识,出口固定写T,入口两个都收。
时间字段被序列化框架拒收。现象:Java 侧解析DateTime抛异常,报文进不来。原因:标准允许yyyy-mm-ddThh:mm:ss.sssZ±hh:mm这种Z与偏移同时出现的写法,而框架的 ISO 解析器只认其中一种。解决:内部统一转 UTC 带Z,本地偏移在展示层算,毫秒位固定补三位。
扩展枚举被当成脏数据。现象:某厂商的设备状态一直显示未知。原因:对方用了x-开头的扩展值,接收端枚举反序列化时直接判非法丢弃。解决:所有枚举解析统一加x-分支,落到「未知但保留原值」的桶里,同时打点统计,扩展值出现频率高就推动纳入下一版映射。
实时数据对不上时间轴。现象:功率曲线和室内温度曲线错位十几分钟,可调容量算出来忽大忽小。原因:RealTimeInfo和TRHParameter都没有时间戳属性,两类数据采样周期不同,靠
本文还有配套的精品资源,点击获取