The official integration guide (overview) for the AECC Energy Management System (AECC Service) open platform. It begins with API authentication and signature generation, then provides core call examples for Dynamic Pricing and Device Service, followed by a FAQ to help you integrate quickly.
All calls to the open APIs go through strict signature verification to ensure communication security and data integrity.
Contact AECC to obtain your dedicated credentials:
key: the signature secret key (used only for local signature generation; never transmit it in plain text over the network)
Credential security recommendations:
Store keys in server-side configuration files or environment variables; never hard-code them in client code
Review key usage regularly (quarterly recommended); contact AECC to reset a key immediately if an anomaly is found
Use different credentials for different environments (test/production) to avoid interference
Restrict key access to core developers only and rotate keys when staff leave
Concatenate the signature source string with the following fixed rules; a wrong order causes verification failure:
Sort business parameters: take all business request parameters (excluding time and sign), sort the parameter names in ascending Unicode code-point order, and join them as key=value&key=value.
Append timestamp and key: at the end of the sorted string, append time={UTC+0 second-level timestamp}&key={assigned key}.
Full example: datalogSn=SXDID888888&deviceSn=SXDID888888XXXXXX&time=1723720871&key=2a1891544dbcf8e8b45b36d03187485a
Detailed steps:
Step 2.1 – Extract business parameters (exclude time and sign):
{
"energyMode": "2",
"aiMode": "0",
"customTimes": "00:00,12:00,1000&13:00,15:00,-2000",
"batRatedCapacity": "1",
"batRatedChargingPower": "1000",
"dataTime": "2025-06-26",
"priceCompany": "Germany"
}
Step 2.2 – Sort parameter names in ascending Unicode order:
aiMode, batRatedCapacity, batRatedChargingPower, customTimes, dataTime, energyMode, priceCompany
Step 2.3 – Concatenate the sorted parameters as key=value joined by &:
aiMode=0&batRatedCapacity=1&batRatedChargingPower=1000&customTimes=00:00,12:00,1000&13:00,15:00,-2000&dataTime=2025-06-26&energyMode=2&priceCompany=Germany
Step 2.4 – Append timestamp and key:
aiMode=0&batRatedCapacity=1&batRatedChargingPower=1000&customTimes=00:00,12:00,1000&13:00,15:00,-2000&dataTime=2025-06-26&energyMode=2&priceCompany=Germany&time=1732756652&key=2a1891544dbcf8e8b45b36d03187485a
Notes:
Empty-string parameter values still participate in the signature (e.g. status=&time=...&key=...)
Special characters are concatenated as-is, without URL encoding
The timestamp must be a second-level timestamp in the UTC+0 time zone, consistent with the server
Use the original key assigned by AECC without any conversion
Apply the standard MD5 algorithm to the string-to-sign and convert the output to an all-lowercase string, which becomes the sign parameter.
Signature examples in multiple languages:
Java:
import java.security.MessageDigest;
import java.util.TreeMap;
public class SignGenerator {
public static String generateSign(TreeMap<String, String> params, String key) {
try {
// Already sorted (TreeMap sorts keys automatically)
StringBuilder sb = new StringBuilder();
// Concatenate business parameters
for (String paramKey : params.keySet()) {
if (!paramKey.equals("sign")) { // exclude sign
sb.append(paramKey).append("=").append(params.get(paramKey)).append("&");
}
}
// Append time and key
sb.append("time=").append(params.get("time")).append("&key=").append(key);
// MD5 and lowercase
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(sb.toString().getBytes("UTF-8"));
StringBuilder hexString = new StringBuilder();
for (byte b : digest) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString().toLowerCase();
} catch (Exception e) {
throw new RuntimeException("Signature generation failed", e);
}
}
}
Python:
import hashlib
def generate_sign(params, key):
"""
Generate the API signature
:param params: business parameter dict (including time)
:param key: assigned key
:return: lowercase MD5 signature string
"""
# Exclude sign, sort by key
sorted_params = sorted([(k, v) for k, v in params.items() if k != 'sign'])
# Concatenate parameters
param_str = '&'.join([f"{k}={v}" for k, v in sorted_params])
# Append time and key
sign_str = f"{param_str}&time={params['time']}&key={key}"
return hashlib.md5(sign_str.encode('utf-8')).hexdigest().lower()
# Usage
params = {
'energyMode': '2',
'aiMode': '0',
'customTimes': '00:00,12:00,1000&13:00,15:00,-2000',
'batRatedCapacity': '1',
'batRatedChargingPower': '1000',
'dataTime': '2025-06-26',
'priceCompany': 'Germany',
'time': '1732756652'
}
sign = generate_sign(params, '2a1891544dbcf8e8b45b36d03187485a')
print(f"Generated signature: {sign}")
JavaScript:
const crypto = require('crypto');
/**
* Generate the API signature
* @param {Object} params - business parameter object (including time)
* @param {string} key - assigned key
* @returns {string} lowercase MD5 signature string
*/
function generateSign(params, key) {
// Sorted keys excluding sign
const sortedKeys = Object.keys(params)
.filter(k => k !== 'sign')
.sort();
const paramString = sortedKeys
.map(k => `${k}=${params[k]}`)
.join('&');
const signString = `${paramString}&time=${params.time}&key=${key}`;
return crypto.createHash('md5')
.update(signString, 'utf8')
.digest('hex')
.toLowerCase();
}
// Usage
const params = {
energyMode: '2',
aiMode: '0',
customTimes: '00:00,12:00,1000&13:00,15:00,-2000',
batRatedCapacity: '1',
batRatedChargingPower: '1000',
dataTime: '2025-06-26',
priceCompany: 'Germany',
time: '1732756652'
};
const sign = generateSign(params, '2a1891544dbcf8e8b45b36d03187485a');
console.log('Generated signature:', sign);
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
public class SignGenerator
{
public static string GenerateSign(Dictionary<string, string> parameters, string key)
{
// Exclude sign, sort by key
var sortedParams = parameters
.Where(p => p.Key != "sign")
.OrderBy(p => p.Key, StringComparer.Ordinal)
.Select(p => $"{p.Key}={p.Value}");
string paramString = string.Join("&", sortedParams);
string signString = $"{paramString}&time={parameters["time"]}&key={key}";
using (var md5 = MD5.Create())
{
byte[] hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(signString));
StringBuilder sb = new StringBuilder();
foreach (byte b in hashBytes)
{
sb.Append(b.ToString("x2")); // lowercase hex
}
return sb.ToString();
}
}
}
Body: besides business parameters, it must contain time (the exact UTC+0 second-level timestamp used in the signature) and sign (the MD5 signature).
Full request example (cURL):
curl --location 'https://monitor.ai-ec.cloud:8443/openApi/price/setEnergyMode' \
--header 'companyCode: AECC2024001' \
--header 'Accept-Language: en-US' \
--header 'Content-Type: application/json' \
--data '{
"energyMode": "2",
"aiMode": "0",
"customTimes": "00:00,12:00,1000&13:00,15:00,-2000",
"batRatedCapacity": "1",
"batRatedChargingPower": "1000",
"dataTime": "2025-06-26",
"priceCompany": "Germany",
"time": "1732756652",
"sign": "c3757db87150d5efbb45009d9253d375"
}'
Server-side verification flow:
Identity check: look up the company key by the companyCode request header
Timestamp check: verify that time is within the allowed window (default ±3600 seconds) to prevent replay attacks
Signature recalculation: recompute the signature with the same rules (sort → append time and key → MD5)
Signature comparison: the computed value must exactly equal the sign in the request
Business processing: only after verification passes is the business logic executed
⚠️ Security notes:
The signature is strongly bound to the timestamp; the server validates the timestamp — do not cache and reuse signatures
Contact AECC immediately to reset a leaked key
Enable HTTPS in production to secure the transport layer
The following examples are grouped into "Dynamic Pricing" and "Device Service" interfaces, covering signature generation, request construction, and response parsing end to end.
Dynamic pricing APIs use the POST /openApi/price/ path prefix and include 4 interfaces: Set Energy Mode, Get Price Region List, Query Price, and Get Price Strategy Time Periods. They are pure server-side computation interfaces that do not involve a specific device: the Body needs no deviceSn and consists of "business parameters + time + sign", where the business parameters vary per interface (e.g. priceCompany, dataTime, energyMode) and all values are JSON strings. Headers are the same as the device APIs (companyCode and Content-Type: application/json are required).
Configures the operating mode of the energy storage system (smart/custom/off) and returns the encrypted control frame to be sent to the data logger.
Request body example:
{
"energyMode": "2",
"aiMode": "0",
"customTimes": "00:00,12:00,1000&13:00,15:00,-2000",
"batRatedCapacity": "1",
"batRatedChargingPower": "1000",
"dataTime": "2025-06-26",
"priceCompany": "Germany",
"time": "1732756652",
"sign": "c3757db87150d5efbb45009d9253d375"
}
| Name | Position | Type | Required | Description |
|---|---|---|---|---|
| companyCode | header | string | Yes | Unique code assigned by AECC; missing → result=10001 |
| Content-Type | header | string | Yes | Fixed to application/json |
| Accept-Language | header | string | No | Response language (e.g. en-US, zh-CN); affects msg only, not result or business fields |
| body | body | object | Yes | Business parameters + time + sign; all values as JSON strings |
| time | body | string | Yes | UTC+0 second-level timestamp, 10 digits, within 3600 s of server time |
| sign | body | string | Yes | 32-character lowercase MD5 signature, recalculated per request |
| priceCompany | body | string | Yes | Price region, e.g. Germany |
| batRatedCapacity | body | string | Yes | Battery rated capacity (kWh), 0–100, energy required for a full charge |
| batRatedChargingPower | body | string | Yes | Battery rated charging power (W); charging time is computed from power and capacity to pick the lowest-price valley |
| dataTime | body | string | Yes | Current date, yyyy-MM-dd |
| energyMode | body | int | Yes | Energy mode: 0 = none (no device control); 1 = Smart mode; 2 = Custom mode |
| aiMode | body | int | No | AI control enable: 0 off / 1 on; only for Smart mode, defaults to off |
| customTimes | body | string | No | Custom charge/discharge time periods, separated by &, up to 16, format e.g. 00:00,12:00,1000&13:00,15:00,-2000 |
| baseDischargePower | body | int | No | Base discharge power |
Key response fields:
packet: hex control frame; must be re-encrypted per the device protocol and a CRC16 checksum computed before being sent to the data logger.
powerTimes: time-period strategy array containing the charge/discharge power commands of each period.
Fetches time-of-use prices for a given region and date, supporting the intelligent control strategy.
Request body example:
{
"dataTime": "2024-09-07",
"priceCompany": "Germany",
"mode": "0",
"time": "1725677116",
"sign": "e07b26034722d166e7f059cb728ab3fd"
}
| Name | Position | Type | Required | Description |
|---|---|---|---|---|
| companyCode | header | string | Yes | Unique code assigned by AECC; missing → result=10001 |
| Content-Type | header | string | Yes | Fixed to application/json |
| Accept-Language | header | string | No | Response language; affects msg only |
| body | body | object | Yes | Business parameters + time + sign; all values as JSON strings |
| dataTime | body | string | Yes | Date, yyyy-MM-dd |
| priceCompany | body | string | Yes | Price region, e.g. Germany |
| mode | body | string | No | Price granularity: 0 = hourly, 1 = 15-minute, default 0 |
| time | body | string | Yes | UTC+0 second-level timestamp, 10 digits, within 3600 s of server time |
| sign | body | string | Yes | 32-character lowercase MD5 signature, recalculated per request |
Key response fields:
Device APIs use the POST /openApi/device/ path prefix. Unlike the pricing APIs, device APIs are device-oriented: only deviceSn is required — no device model code (DTC) — and the server routes by the authorized deviceSn, automatically selecting the response structure and configurable parameters; the query Body contains only deviceSn + time + sign. Query APIs (Examples 3 and 4, plus getSetInfo) do not require the device to be online; setting APIs (Examples 5 and 6) require the device to be online and return result=20008 when offline.
7 meter models are available in this release: RS07 (DTC 65417), RS06 (DTC 60675), RS02 / RC01 / RS09 (DTC 88801, AECC's own externally connected meters sharing the externally connected meter entity), Smart Wireless Three-Phase CT Meter – Single-Circuit (DTC 10002 / 65296 / 65348), and Smart Meter – DIN-Rail Collector WiFi+BLE (DTC 65389). Requests do not carry the DTC; parse responses according to the model confirmed in the activation order.
Field summary per model:
| Model | Rated info (getBasicsInfo) | Real-time info (getRealTimeInfo) |
|---|---|---|
| Smart Wireless Three-Phase CT Meter (Single-Circuit) | lineType, currentReverseSet, ctType | ctVersion, three-phase voltage/current, active/reactive/apparent power, power factor, frequency, forward/reverse energy, load identification — 36 fields (power factor fields start with a lowercase letter, e.g. aphasePowerFactor) |
| RS07 | Dual-circuit three-phase rated/settings entity: 485 address, two circuits' current-reverse / CT-phase-sequence / CT-ratio — 13 fields | Dual-circuit three-phase real-time entity: per circuit L1/L2/L3 voltage, current, power, phase angle, energy — 114 fields, plus the openDatalogVo meter module attribute object |
| RS06 | No standalone rated entity; returns deviceVo=null | RS06 real-time entity: three-phase voltage/current/active power, per-phase energy, power factor, fault code, total grid/bought/sold energy — 22 fields |
| RS02 / RC01 / RS09 | Externally connected meter entity with all business fields null; rated data via the real-time API | Externally connected meter real-time entity: thirdPartyType, power/voltage/current/frequency, ctTotalUseEnergy, etc. — 20 fields |
| Smart Meter – DIN-Rail Collector WiFi+BLE | ctType plus both circuits' CT polarity/phase-sequence — 17 fields | Full per-circuit (suffix 1/2) voltage, current, power, and energy fields |
Full field tables per model: see the Full Version document, Device Service chapter.
Empty business data: when a binding exists but rated/real-time/settings data has not been reported yet, the API may still return result=0 with obj.deviceVo=null (obj.deviceSn and obj.dataLogSn may also be null). This is normal. Processing order: check result=0 first, then whether obj/obj.deviceVo is null, and only then read business fields. Field order is not a contract — parse JSON by field name.
Fetches the rated parameters of a device.
Request body example:
{
"deviceSn": "NB2548300T110CHAB",
"time": "1725450897",
"sign": "e83ba9c021edd831ae69033f77528ac5"
}
| Name | Position | Type | Required | Description |
|---|---|---|---|---|
| companyCode | header | string | Yes | Unique code assigned by AECC; missing → result=10001 |
| Content-Type | header | string | Yes | Fixed to application/json |
| Accept-Language | header | string | No | Response language; affects msg only |
| body | body | object | Yes | Only the business parameters below; all values as JSON strings |
| deviceSn | body | string | Yes | Device serial number; no device type or DTC needed — the server routes by the bound model |
| time | body | string | Yes | UTC+0 second-level timestamp, 10 digits, within 3600 s of server time |
| sign | body | string | Yes | 32-character lowercase MD5 signature, recalculated per request |
Response example (inverter device, excerpt):
{
"result": 0,
"msg": "Request successfully.",
"obj": {
"deviceSn": "NB2548300T110CHAB",
"dataLogSn": "AECCAA0437",
"deviceState": 1,
"deviceVo": {
"serialNumber": "NB2548300T110CHAB",
"ratedPower": 110.0,
"mpptCount": 9,
"stringCount": 18,
"pvStartVoltage": 180.0,
"gridVoltageType": 1,
"mainSDPVersion": "V1.2.3"
}
},
"data": null,
"taskResult": null
}
The excerpt shows common rated fields only; see the Full Version document for all fields.
Response example (meter device, Smart Wireless Three-Phase CT Meter – Single-Circuit):
{
"result": 0,
"msg": "Request successfully.",
"obj": {
"deviceSn": "EXAMPLE-METER-SN",
"dataLogSn": "EXAMPLE-DATALOG-SN",
"deviceState": 0,
"deviceVo": {
"lineType": 1,
"currentReverseSet": 0,
"ctType": 23
}
},
"data": null,
"taskResult": null
}
RS07 returns the dual-circuit three-phase rated/settings entity (13 fields); RS06 rated always returns deviceVo=null; the DIN-rail collector returns its rated/settings entity (17 fields); RS02/RC01/RS09 return the externally connected meter entity with all business fields null — rated data is available via the real-time API.
Fetches the real-time data of a device.
Request body example:
{
"deviceSn": "NB2548300T110CHAB",
"time": "1725450897",
"sign": "e83ba9c021edd831ae69033f77528ac5"
}
(Request parameters are identical to Example 3.)
Response example (inverter device, excerpt):
{
"result": 0,
"msg": "Request successfully.",
"obj": {
"deviceSn": "NB2548300T110CHAB",
"dataLogSn": "AECCAA0437",
"deviceState": 1,
"deviceVo": {
"runStatus": 1,
"pvTotalPower": 0.0,
"dailyPvGenEnergy": 717.2,
"totalPvGenEnergy": 29884.8,
"dailyGridConnectGenEnergy": 678.9,
"totalGridConnectGenEnergy": 28431.2,
"gridTotalActivePower": null
}
},
"data": null,
"taskResult": null
}
The excerpt shows common real-time fields only; see the Full Version document for all fields.
Response example (meter device, Smart Wireless Three-Phase CT Meter – Single-Circuit):
{
"result": 0,
"msg": "Request successfully.",
"obj": {
"deviceSn": "EXAMPLE-METER-SN",
"dataLogSn": "EXAMPLE-DATALOG-SN",
"deviceState": 0,
"deviceVo": {
"ctAPhaseVoltage": 220.1,
"ctBPhaseVoltage": 220.2,
"ctCPhaseVoltage": 220.3,
"ctAPhaseCurrent": 10.1,
"ctBPhaseCurrent": 10.2,
"ctCPhaseCurrent": 10.3,
"ctThreePhaseTotalPower": 3000.6,
"aphasePowerFactor": 0.99,
"totalPowerFactor": 0.98,
"frequency": 50.01,
"forwardActiveEnergy": 123.45,
"reverseActiveEnergy": 1.23,
"totalActiveEnergy": 124.68,
"ctType": 23
}
},
"data": null,
"taskResult": null
}
RS07 returns the dual-circuit three-phase real-time entity (114 fields, including the openDatalogVo meter module attribute object); RS06 returns the RS06 real-time entity (22 fields); the DIN-rail collector returns its dual-circuit real-time entity; RS02/RC01/RS09 return the externally connected meter real-time entity (thirdPartyType, power/voltage/current/frequency, ctTotalUseEnergy, etc.). In the single-circuit real-time response the power factor and apparent power field names start with a lowercase letter (e.g. aphasePowerFactor, not aPhasePowerFactor). This API returns the latest real-time snapshot only — no time-range queries, pagination, or history.
Sets one parameter of a device. Beforehand you can query the configurable items and their server-side values via getSetInfo (POST /openApi/device/getSetInfo, same request as Example 3, different URL).
Request example (inverter device):
{
"deviceSn": "NB2548300T110CHAB",
"paramName": "pvStartVoltage",
"paramValue": "179",
"time": "1785461710",
"sign": "<md5-sign>"
}
Response example (inverter device, obj echoes the setting):
{
"result": 0,
"msg": "Setting successful.",
"obj": {
"pvStartVoltage": "179"
},
"data": null,
"taskResult": null
}
Request example (meter device, Smart Wireless Three-Phase CT Meter – Single-Circuit):
{
"deviceSn": "<your-deviceSn>",
"paramName": "currentReverseSet",
"paramValue": "1",
"time": "<utc-second-time>",
"sign": "<md5-sign>"
}
Response example (meter device, obj is null):
{
"result": 0,
"msg": "Setting successful.",
"obj": null,
"data": null,
"taskResult": null
}
Key rules:
paramName is case-sensitive. Settable meter fields: Smart Wireless Three-Phase CT Meter (Single-Circuit) supports only currentReverseSet (current reverse setting); Smart Meter – DIN-Rail Collector WiFi+BLE supports transformerPolarityAdjustA1/B1/C1/A2/B2/C2 (6 CT polarity adjustment fields); RS06, RS07, RS02, RC01, and RS09 do not support setting (online → result=1 "not supported"; offline → result=20008 first). Meter paramValue must be "0" or "1"; unknown parameter names or invalid values return a business failure.
Setting APIs require the device online (offline → result=20008); a normal call returns in about 1 second, and about 5 seconds when the device does not answer (timeout) — the client read timeout should exceed 5 seconds.
result=0 means the device acknowledged the write and the server saved the value; do not retry immediately on timeout or failure — re-check via getSetInfo first or retry after an interval, ensuring the previous setting request has returned.
Sets several parameters in one call; object is a serialized JSON string (not a nested JSON object) whose value must match the signed string character for character — do not reformat or reorder fields after signing.
Request example (inverter device):
{
"deviceSn": "NB2548300T110CHAB",
"object": "{\"underVoltProtectRecoverValue\":\"197\",\"overVoltProtectRecoverValue\":\"250\"}",
"time": "1785462195",
"sign": "<md5-sign>"
}
Response example (inverter device, obj echoes the settings):
{
"result": 0,
"msg": "Setting successful.",
"obj": {
"underVoltProtectRecoverValue": "197",
"overVoltProtectRecoverValue": "250"
},
"data": null,
"taskResult": null
}
Request example (meter device, Smart Wireless Three-Phase CT Meter – Single-Circuit):
{
"deviceSn": "<your-deviceSn>",
"object": "{\"currentReverseSet\":\"1\"}",
"time": "<utc-second-time>",
"sign": "<md5-sign>"
}
Request example (meter device, DIN-rail collector – same-circuit multi-field):
{
"deviceSn": "<your-deviceSn>",
"object": "{\"transformerPolarityAdjustA1\":\"1\",\"transformerPolarityAdjustB1\":\"0\",\"transformerPolarityAdjustC1\":\"1\"}",
"time": "<utc-second-time>",
"sign": "<md5-sign>"
}
Response example (meter device): same as Example 5 — result=0, obj=null.
Key rules:
Meter object field values are all strings "0" or "1". Smart Wireless Three-Phase CT Meter (Single-Circuit) must contain exactly one field, currentReverseSet; Smart Meter – DIN-Rail Collector accepts 1–3 fields of the same circuit (first or second), and first- and second-circuit fields must not be mixed in one request, otherwise the whole request fails and nothing is sent; RS06, RS07, RS02, RC01, and RS09 do not support setting.
object must not be an empty object, an empty string, invalid JSON, an array, or contain unknown fields; no inner field value may be null. Only submitted fields are updated after success; other fields of the same circuit keep their values.
Online requirements, timeout behavior, and failure handling are the same as Example 5: offline → result=20008; on timeout do not retry immediately — re-check via getSetInfo first.
The server recomputes the signature with the same rules (business parameters sorted in Unicode ascending order → append time and key → MD5 → lowercase) and compares it with the sign in the request body using exact comparison (case-sensitive, no trimming). Any mismatch is reported as result=10001, msg="Signature exception".
Note: signature errors, expired timestamps, and missing companyCode/time/sign all merge into the same 10001 code on the server and cannot be distinguished by code alone — check the steps below one by one.
Troubleshooting steps (ordered by hit rate):
Confirm sign is a 32-character lowercase hex string
The MD5 output must be all lowercase. Uppercase or Base64 will always fail.
// Correct: Spring DigestUtils already returns 32-character lowercase
String sign = DigestUtils.md5DigestAsHex(text.getBytes(StandardCharsets.UTF_8));
Confirm the concatenation order
Business parameter names must be sorted in ascending Unicode (lexicographic) order — use TreeMap (Java) / sorted() (Python) / .sort() (JS). The result should be k1=v1&k2=v2&...&time=xxx&key=xxx.
Confirm sign and time are excluded from sorting
Only business parameters are sorted; time is always appended at the end, and key after it. sign itself is not part of the source string.
Confirm values are concatenated as-is — no URL encoding, no trimming
Empty-string values still participate (e.g. status=&time=...).
Special characters such as &, :, , (e.g. the customTimes field) are concatenated verbatim — do not URL-encode.
Leading/trailing spaces become part of the MD5 input; make sure nothing trims them.
Confirm the original key assigned by AECC is used
The server looks up keySecret by the companyCode header and appends it. The client must use the same original key without any encoding/truncation/conversion. If a key is suspected to be wrong or leaked, contact AECC to reset it.
Confirm time is identical in the source string and the request body
time appears in both; they must match character for character, otherwise the server recomputes a different signature.
Reproduce the server algorithm locally
Print the string-to-sign and compare its MD5 with the expected value. If it still mismatches, send the string-to-sign and your sign to AECC for assistance.
Server-side recomputation (reference):
// 1. Sort business parameters with TreeMap (exclude sign and time)
// 2. Append &time={time}&key={keySecret}
// 3. MD5(text.getBytes("UTF-8")) → 32-char lowercase hex
if (md5.equals(requestSign)) { /* pass */ }
Format: a second-level Unix timestamp in the UTC+0 time zone (10 digits, e.g. 1732756652). Do not use millisecond (13-digit) values or local-time strings.
Valid window: the server compares time with the current UTC+0 timestamp and allows a deviation of ±3600 seconds (±1 hour). Requests outside the window return result=10001, msg="Signature exception".
Clock drift advice:
Follow the server: generate time = current UTC+0 second-level timestamp for every request; never cache or reuse timestamps, since signatures are time-bound.
Synchronize clocks: if your server clock deviates by more than tens of seconds, enable NTP synchronization (e.g. ntpd/chrony on Linux, w32time on Windows).
The window cannot be widened: ±1 hour is a fixed server policy; the only fix for larger drift is clock synchronization.
Containers/VMs: clocks may jump after restarts — synchronize time at container start.
No. The key must never appear in any network request.
Sole use of the key: local signature computation only (appended as &key=xxx to the string-to-sign). It is never a request parameter, header, or URL query.
How the server gets it: the server looks up the keySecret by the companyCode header. companyCode travels with the request; the key never does.
Correct usage:
companyCode in the header: --header 'companyCode: AECC2024001'
key only in local MD5 computation.
Security: store keys in server-side configuration or environment variables; never hard-code, log, or transmit them. Reset immediately if a leak is suspected.
The HTTP layer normally returns 200; business results are expressed by result in the body. True system errors map to result=4000, msg="System error, please contact the administrator!". Recommended strategy:
1. Client timeouts
Use sensible connect/read timeouts (e.g. 5 s / 10 s) to avoid threads blocking on network jitter:
// HttpClient example
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(5000) // connect timeout 5s
.setSocketTimeout(10000) // read timeout 10s
.build();
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(config);
2. Retrying result=4000 (system error)
Usually a transient server issue; retrying is fine.
Use exponential backoff: 1s → 2s → 4s, up to 3 times; always regenerate time and sign — never replay the original request.
If 4000 persists, contact AECC with companyCode, request time, and deviceSn/endpoint.
3. Network timeouts (no response)
Apply the same exponential backoff on SocketTimeoutException / connection refused.
After repeated timeouts, check local network and DNS first, then contact AECC.
4. Do not retry
result=10001 (signature error): retrying changes nothing; fix the signature/timestamp first.
result=20001 (missing parameters) / 20004 (parameter format) / other 2000x business validation errors: fix the parameters first.
result=10006 (account disabled) / 20002 (permission, incl. missing/disabled credentials): contact AECC.
5. Rate limiting
Some interfaces are rate-limited. On a limit response, reduce the call frequency and do not retry immediately.
Note: the status field is actually named result (not code); "status code" is used below for readability.
Response structure:
{
"result": 0,
"msg": "Request successfully.",
"data": { /* business data */ }
}
result: status code — 0 = success, non-zero = failure.
msg: description text, internationalized via the Accept-Language header (e.g. en-US, zh-CN); defaults to en-US.
data / obj: business data carrier.
Common status codes:
| result | Meaning | Typical msg (en-US) | Trigger scenario |
|---|---|---|---|
| 0 | Success | Request successfully. | Business completed normally |
| 1 | Generic failure | (caller-provided) | Business validation failed — msg explains; or the URL is not opened for the companyCode; meter setting errors (unsupported parameter name, value not 0/1, invalid object, both rail-collector circuits submitted, RS06/RS07/RS02/RC01/RS09 not supporting setting, setting timeout/failure) |
| 10000 | Not logged in / token expired | Please login. / token expired | Invalid or expired claims in token mode |
| 10001 | Signature error | Signature exception | Wrong signature, expired timestamp, or missing companyCode/time/sign (merged code, not distinguishable) |
| 10002 | Invalid email format | Incorrect email format. | Malformed email in registration/binding |
| 10006 | User disabled | The user has been disabled. | Account locked |
| 20000 | Wrong parameter type | Incorrect parameter type. | Parameter type mismatch |
| 20001 | Data required | Submitted data cannot be empty. | Required parameter missing; or business data missing (e.g. no meter settings row) |
| 20002 | Permission error | Abnormal permissions. | openApi auth path: companyCode does not exist, credentials not enabled (flagState != 1), or no permission |
| 20003 | Time format error | Time format error. | Invalid date/time parameter |
| 20004 | Parameter format error | Parameter format error. | Parameter format validation failed |
| 20005 | No such parameter | No this parameter. | Required business parameter missing |
| 20006 | System error | System error, try later. | Server business exception |
| 20007 | DTC not found | DeviceCode does not exist | Invalid device type code; or the bound device type has no open-platform implementation |
| 20008 | Device offline | Device off-line | No data reported; setting APIs require the device online and return this code when offline |
| 20009 | Device not found | Device not exist | deviceSn not found in the system |
| 4000 | Global system error | System error, please contact the administrator! | Uncaught exceptions, transaction rollbacks, parsing errors (retry or contact AECC) |
Troubleshooting tips: