简介《电力需求响应信息模型 第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的unitW(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 用法VoltageVVk10 kV 进线CurrentFlowAA空或 kFrequencyHzHz空ApparentPowerVAVAkActivePowerWWk机组铭牌功率ReactivePowervarvarkRealEnergyWhWhk 或 MTemperaturedegCT空SpecificHeatCapacityJ/kg*KJ/kg*K空ThermalResistance℃/W℃/W空PressurePaPakVolumem³m3空Densitykg/m³kg/m3空FlowRatem3/sm3/s空WindSpeedm/sm/s空IlluminationLxLx空CalorificValueJ/kgJ/kg空RotationSpeedrpmrpm空温度那一行是必须单独拎出来说的Temperature类型定义里写的是unitdegC(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 8601UTC 时区用尾部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 kWunit恒为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类型定义写degCUnitSymbol枚举字面量写T两边各取一边。解决适配层建别名表T和degC都映射到内部统一标识出口固定写T入口两个都收。时间字段被序列化框架拒收。现象Java 侧解析DateTime抛异常报文进不来。原因标准允许yyyy-mm-ddThh:mm:ss.sssZ±hh:mm这种Z与偏移同时出现的写法而框架的 ISO 解析器只认其中一种。解决内部统一转 UTC 带Z本地偏移在展示层算毫秒位固定补三位。扩展枚举被当成脏数据。现象某厂商的设备状态一直显示未知。原因对方用了x-开头的扩展值接收端枚举反序列化时直接判非法丢弃。解决所有枚举解析统一加x-分支落到「未知但保留原值」的桶里同时打点统计扩展值出现频率高就推动纳入下一版映射。实时数据对不上时间轴。现象功率曲线和室内温度曲线错位十几分钟可调容量算出来忽大忽小。原因RealTimeInfo和TRHParameter都没有时间戳属性两类数据采样周期不同靠本文还有配套的精品资源点击获取