基于 OpenHarmony 6.0 的智能充电桩技术方案与实现

1. 引言与概述

本项目旨在基于 OpenHarmony 操作系统,开发一款符合行业标准的智能交流/直流充电桩。核心技术挑战在于实现与后台管理系统的稳定通信(OCPP)、高精度的电能计量与校准,以及在复杂网络环境下的可靠数据同步。

本文档将围绕以下三个核心模块展开,详细阐述其设计思路、技术选型、具体实现及模块间的整合方案:

  1. OCPP 1.6/2.0.1 通信协议栈:实现充电桩与中央管理系统(CMS)的实时双向通信。
  2. 电能计量与校准模块:确保充电电量数据的精确性和可追溯性。
  3. 车-桩-云协同与数据同步:保障充电记录在弱网或断网情况下的完整性与最终一致性。

2. 系统架构

系统采用分层架构设计,从上至下分为:应用层、框架与服务层、以及驱动与硬件层。

架构说明:

  • 应用层:使用 ArkTS 编写业务逻辑,包括 OCPP 状态机、计量服务、数据同步服务和用户界面。
  • 框架与服务层:利用 OpenHarmony 提供的系统能力,如网络通信(WebSocket)、安全存储(Huks)、分布式数据库等。
  • 驱动与硬件层:通过自定义 NAPI 模块桥接 ArkTS 与 HDF 驱动框架,与底层硬件(如 ADC、电表芯片)交互。

3. 核心功能实现

3.1 OCPP 1.6/2.0.1 通信协议实现
3.1.1 设计思路

OCPP(Open Charge Point Protocol)是基于 JSON 的 WebSocket 协议。我们在 ArkTS 层构建一个状态机来管理连接和消息流,并将充电会话与 Ability 生命周期关联。

3.1.2 技术选型
  • 语言/框架:ArkTS
  • 网络通信@kit.NetworkKit (WebSocket)
3.1.3 代码实现

1. OCPP 状态机管理器 (model/OcppManager.ets)

typescript 复制代码
// model/OcppManager.ets
import { webSocket } from '@kit.NetworkKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
enum OcppState {
  DISCONNECTED = 'Disconnected',
  CONNECTING = 'Connecting',
  CONNECTED = 'Connected',
  REGISTERED = 'Registered',
}
const OCPP_MESSAGE_TYPE = {
  CALL: 2,
  CALL_RESULT: 3,
  CALL_ERROR: 4,
};
export class OcppManager {
  private ws: webSocket.WebSocket | null = null;
  private state: OcppState = OcppState.DISCONNECTED;
  private serverUrl: string = 'ws://your.cms.server/ocpp/';
  private chargePointId: string = 'CHARGE_POINT_001';
  private messageId: number = 1;
  constructor() {
    this.connect();
  }
  private connect(): void {
    if (this.state !== OcppState.DISCONNECTED) return;
    this.setState(OcppState.CONNECTING);
    this.ws = webSocket.createWebSocket();
    
    this.ws.on('open', (err: BusinessError) => {
      if (err) {
        hilog.error(0x0000, 'OcppManager', `WebSocket open error: ${JSON.stringify(err)}`);
        this.setState(OcppState.DISCONNECTED);
        return;
      }
      hilog.info(0x0000, 'OcppManager', 'WebSocket connected.');
      this.setState(OcppState.CONNECTED);
      this.sendBootNotification();
    });
    this.ws.on('message', (err: BusinessError, value: string | ArrayBuffer) => {
      if (err) return;
      this.handleIncomingMessage(value as string);
    });
    this.ws.on('close', () => {
      hilog.info(0x0000, 'OcppManager', 'WebSocket closed.');
      this.setState(OcppState.DISCONNECTED);
      // Implement reconnection logic here
    });
    this.ws.on('error', (err: BusinessError) => {
      hilog.error(0x0000, 'OcppManager', `WebSocket error: ${JSON.stringify(err)}`);
      this.setState(OcppState.DISCONNECTED);
    });
    this.ws.connect(this.serverUrl);
  }
  private setState(newState: OcppState): void {
    hilog.info(0x0000, 'OcppManager', `State transition: ${this.state} -> ${newState}`);
    this.state = newState;
  }
  private sendBootNotification(): void {
    const payload = { chargePointModel: "OH Charger", chargePointVendor: "MyCompany" };
    this.send('BootNotification', payload);
  }
  public send(action: string, payload: Object): void {
    if (this.state !== OcppState.CONNECTED && this.state !== OcppState.REGISTERED) return;
    const message = [OCPP_MESSAGE_TYPE.CALL, this.messageId++, action, payload];
    this.ws?.send(JSON.stringify(message));
  }
  private handleIncomingMessage(message: string): void {
    const [messageType, messageId, , payload] = JSON.parse(message);
    if (messageType === OCPP_MESSAGE_TYPE.CALL_RESULT && messageId === 1) {
      if (payload.status === 'Accepted') {
        this.setState(OcppState.REGISTERED);
        hilog.info(0x0000, 'OcppManager', 'Charge point registered.');
      }
    }
  }
  public startTransaction(connectorId: number, idTag: string): void {
    const payload = { connectorId, idTag, meterStart: 0, timestamp: new Date().toISOString() };
    this.send('StartTransaction', payload);
  }
  
  public stopTransaction(transactionId: number, idTag: string): void {
    const payload = { transactionId, idTag, meterStop: 1000, timestamp: new Date().toISOString() };
    this.send('StopTransaction', payload);
  }
  public getState(): OcppState {
    return this.state;
  }
}
3.1.4 讲解
  • 状态机OcppState 枚举管理连接生命周期,setState 方法封装状态切换。
  • WebSocket 封装 :使用 @kit.NetworkKit 的 WebSocket API,监听核心事件,实现异步通信。
  • 消息格式 :严格遵循 OCPP-J [messageType, messageId, action, payload] 格式。
  • 生命周期映射startTransactionstopTransaction 方法可供 UI Ability 在其生命周期回调中调用,实现充电会话与用户交互的关联。

3.2 电能计量校准
3.2.1 设计思路

ArkTS 应用无法直接访问硬件。我们通过 NAPI 模块作为桥梁,连接 ArkTS 与 C++ 编写的 HDF 驱动代码,实现 ADC 数据读取。校准系数使用 Huks 加密后,通过文件系统存储。

3.2.2 技术选型
  • 硬件交互 :HDF ADC Driver + 自定义 NAPI 模块
  • 安全存储@kit.UnifiedKeyStoreKit (Huks) + @ohos.file.fs
3.2.3 代码实现

第一步:编写 C++ NAPI 模块 (src/main/cpp/adc_reader.cpp)

cpp 复制代码
// src/main/cpp/adc_reader.cpp
#include "napi/native_api.h"
#include <hdf_log.h>
#include <adc_if.h>
#define ADC_DEVICE_NUM 0
#define ADC_CHANNEL_NUM 1
static napi_value ReadAdc(napi_env env, napi_callback_info info) {
    size_t argc = 2;
    napi_value args[2];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    int32_t deviceNum, channelNum;
    napi_get_value_int32(env, args[0], &deviceNum);
    napi_get_value_int32(env, args[1], &channelNum);
    DevHandle *handle = AdcOpen(deviceNum);
    if (handle == nullptr) {
        HDF_LOGE("AdcOpen failed");
        return nullptr;
    }
    uint32_t readData;
    int32_t ret = AdcRead(handle, channelNum, &readData);
    AdcClose(handle);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("AdcRead failed, ret: %d", ret);
        return nullptr;
    }
    
    HDF_LOGI("ADC Read Success: %u", readData);
    napi_value result;
    napi_create_uint32(env, readData, &result);
    return result;
}
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        { "readAdc", nullptr, ReadAdc, nullptr, nullptr, nullptr, napi_default, nullptr },
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
NAPI_MODULE(NAPI_GITHUB_MODULE_NAME, Init)

第二步:配置编译 (src/main/cpp/BUILD.gn)

gn 复制代码
# src/main/cpp/BUILD.gn
import("//build/ohos.gni")
ohos_shared_library("adc_reader") {
  sources = [ "adc_reader.cpp" ]
  include_dirs = [
    "//drivers/hdf_core/adapter/uhdf2/include",
    "//drivers/hdf_core/framework/include/core",
    "//drivers/hdf_core/framework/include/utils",
    "//third_party/bounds_checking_function/include",
  ]
  deps = [
    "//drivers/hdf_core/adapter/uhdf2:libhdf",
    "//drivers/hdf_core/adapter/uhdf2/metadata:libhdf_meta",
  ]
  external_deps = [ "hiviewdfx_hilog_native:libhilog" ]
  output_name = "adc_reader" # ArkTS 中 import 时使用
  subsystem_name = "charger"
  part_name = "charger_part"
}

第三步:ArkTS 层调用 (model/MeteringService.ets)

typescript 复制代码
// model/MeteringService.ets
import { hilog } from '@kit.PerformanceAnalysisKit';
import { common } from '@kit.AbilityKit';
import { HuksHelper } from './HuksHelper';
import adcReader from 'libadc_reader.so'; // 导入 NAPI 模块
interface CalibrationFactors { offset: number; scale: number; }
export class MeteringService {
  private calibrationFactors: CalibrationFactors = { offset: 0, scale: 1.0 };
  private readonly CALIBRATION_KEY_ALIAS = 'metering_calibration_factors';
  private context: common.UIAbilityContext;
  constructor(context: common.UIAbilityContext) {
    this.context = context;
    this.init();
  }
  private async init(): Promise<void> {
    await this.loadCalibrationFactors();
  }
  private async loadCalibrationFactors(): Promise<void> {
    try {
      const encryptedData = await HuksHelper.getData(this.context, 'calibration.bin');
      if (encryptedData) {
        const decryptedData = await HuksHelper.decrypt(this.CALIBRATION_KEY_ALIAS, encryptedData);
        this.calibrationFactors = JSON.parse(decryptedData);
        hilog.info(0x0000, 'MeteringService', `Calibration loaded: ${JSON.stringify(this.calibrationFactors)}`);
      }
    } catch (error) {
      hilog.error(0x0000, 'MeteringService', `Failed to load calibration: ${JSON.stringify(error)}`);
    }
  }
  public async calibrateAndSave(standardValue: number): Promise<void> {
    const rawValue = await this.getRawValue(0, 1);
    const newScale = rawValue > 0 ? standardValue / rawValue : 1.0;
    this.calibrationFactors = { offset: 0, scale: newScale };
    hilog.info(0x0000, 'MeteringService', `New calibration: ${JSON.stringify(this.calibrationFactors)}`);
    
    try {
      const plainData = JSON.stringify(this.calibrationFactors);
      const encryptedData = await HuksHelper.encrypt(this.CALIBRATION_KEY_ALIAS, plainData);
      await HuksHelper.saveData(this.context, 'calibration.bin', encryptedData);
    } catch (error) {
      hilog.error(0x0000, 'MeteringService', `Failed to save calibration: ${JSON.stringify(error)}`);
    }
  }
  private async getRawValue(device: number, channel: number): Promise<number> {
    try {
      return adcReader.readAdc(device, channel);
    } catch (error) {
      hilog.error(0x0000, 'MeteringService', `NAPI read failed: ${JSON.stringify(error)}`);
      return 0;
    }
  }
  public async getCalibratedEnergy(): Promise<number> {
    const rawValue = await this.getRawValue(0, 1);
    return (rawValue - this.calibrationFactors.offset) * this.calibrationFactors.scale;
  }
}

第四步:Huks 辅助类 (model/HuksHelper.ets)

typescript 复制代码
// model/HuksHelper.ets
import { huks } from '@kit.UnifiedKeyStoreKit';
import { HksKeyAlg, HksKeyPurpose, HksTag } from '@kit.UnifiedKeyStoreKit';
import { fs } from '@ohos.file.fs';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
export class HuksHelper {
  private static readonly OPTIONS: huks.HuksOptions = {
    properties: [
      { tag: HksTag.HKS_TAG_ALGORITHM, value: HksKeyAlg.HKS_ALG_AES },
      { tag: HksTag.HKS_TAG_PURPOSE, value: HksKeyPurpose.HKS_KEY_PURPOSE_ENCRYPT | HksKeyPurpose.HKS_KEY_PURPOSE_DECRYPT },
      { tag: HksTag.HKS_TAG_KEY_SIZE, value: 256 },
      { tag: HksTag.HKS_TAG_BLOCK_MODE, value: huks.HksKeyBlockMode.HKS_MODE_GCM }
    ],
    inData: new Uint8Array()
  };
  public static async getKey(alias: string): Promise<boolean> {
    try { await huks.isKeyExist(alias, this.OPTIONS); return true; }
    catch {
      try { await huks.generateKey(alias, this.OPTIONS); return true; }
      catch (e) { hilog.error(0x0000, 'HuksHelper', `Gen key failed: ${JSON.stringify(e)}`); return false; }
    }
  }
  public static async encrypt(alias: string, plainText: string): Promise<Uint8Array> {
    if (!(await this.getKey(alias))) throw new Error("Key not available");
    const options = { ...this.OPTIONS, inData: new Uint8Array(buffer.from(plainText, 'utf8')) };
    const result = await huks.encrypt(alias, options);
    return result.outData;
  }
  public static async decrypt(alias: string, cipherText: Uint8Array): Promise<string> {
    if (!(await this.getKey(alias))) throw new Error("Key not available");
    const options = { ...this.OPTIONS, inData: cipherText };
    const result = await huks.decrypt(alias, options);
    return String.fromCharCode.apply(null, result.outData);
  }
  
  public static async saveData(context: common.UIAbilityContext, fileName: string, data: Uint8Array): Promise<void> {
    const filePath = context.filesDir + '/' + fileName;
    const file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
    fs.writeSync(file.fd, data);
    fs.closeSync(file);
  }
  public static async getData(context: common.UIAbilityContext, fileName: string): Promise<Uint8Array | null> {
    try {
      const filePath = context.filesDir + '/' + fileName;
      const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
      const stat = fs.statSync(filePath);
      const buffer = new ArrayBuffer(stat.size);
      fs.readSync(file.fd, buffer);
      fs.closeSync(file);
      return new Uint8Array(buffer);
    } catch (error) {
      if ((error as BusinessError).code === 13900002) return null; // File not found
      hilog.error(0x0000, 'HuksHelper', `Read file error: ${JSON.stringify(error)}`);
      return null;
    }
  }
}
3.2.4 讲解
  • NAPI 桥梁adc_reader.cpp 使用 HDF 的 C 接口读取 ADC,并通过 NAPI 将其暴露给 ArkTS。BUILD.gn 是编译此 C++ 代码为动态库的关键。
  • ArkTS 调用 :通过 import adcReader from 'libadc_reader.so' 加载模块,并像调用普通 JS 函数一样调用 adcReader.readAdc()
  • 安全存储HuksHelper 完整实现了加密数据到文件的存取流程,MeteringService 依赖它来安全地管理校准系数。

3.3 车-桩-云协同与数据同步
3.3.1 设计思路

所有充电记录优先存入本地分布式数据库(KvStore),并加入一个"待同步队列"。通过监听网络状态,在网络恢复时自动将队列中的数据续传至云端,实现弱网补偿。

3.3.2 技术选型
  • 本地数据库@kit.ArkData (distributedKVStore)
  • 网络状态监听@kit.NetworkKit
3.3.3 代码实现
typescript 复制代码
// model/DataSyncManager.ets
import { distributedKVStore } from '@kit.ArkData';
import { connection } from '@kit.NetworkKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { common } from '@kit.AbilityKit';
import { OcppManager } from './OcppManager';
import { OcppState } from './OcppManager'; // Import the enum
interface ChargingRecord {
  sessionId: string;
  startTime: string;
  endTime?: string;
  energy: number;
  synced: boolean;
}
export class DataSyncManager {
  private kvStore: distributedKVStore.SingleKVStore | null = null;
  private netConnection: connection.NetConnection | null = null;
  private readonly SYNC_QUEUE_KEY = 'sync_queue';
  private readonly STORE_ID = 'charging_records_db';
  private ocppManager: OcppManager;
  private context: common.UIAbilityContext;
  constructor(context: common.UIAbilityContext, ocppManager: OcppManager) {
    this.context = context;
    this.ocppManager = ocppManager;
    this.init();
  }
  private async init(): Promise<void> {
    await this.initKVStore();
    this.setupNetworkListener();
  }
  private async initKVStore(): Promise<void> {
    try {
      const kvStoreConfig: distributedKVStore.KVStoreConfig = { storeId: this.STORE_ID, context: this.context };
      const kvStoreManager = distributedKVStore.createKVStoreManager(this.context);
      this.kvStore = await kvStoreManager.getKVStore(kvStoreConfig);
      hilog.info(0x0000, 'DataSyncManager', 'KVStore initialized.');
    } catch (error) {
      hilog.error(0x0000, 'DataSyncManager', `KVStore init failed: ${JSON.stringify(error)}`);
    }
  }
  private setupNetworkListener(): void {
    this.netConnection = connection.createNetConnection();
    this.netConnection.on('netAvailable', () => {
      hilog.info(0x0000, 'DataSyncManager', 'Network available. Starting sync.');
      this.syncToCloud();
    });
    this.netConnection.register();
  }
  public async saveRecord(record: ChargingRecord): Promise<void> {
    if (!this.kvStore) return;
    try {
      await this.kvStore.put(record.sessionId, JSON.stringify(record));
      const queueData = await this.kvStore.get(this.SYNC_QUEUE_KEY, '[]');
      const queue: string[] = JSON.parse(queueData as string);
      if (!queue.includes(record.sessionId)) {
        queue.push(record.sessionId);
        await this.kvStore.put(this.SYNC_QUEUE_KEY, JSON.stringify(queue));
      }
      hilog.info(0x0000, 'DataSyncManager', `Record ${record.sessionId} saved.`);
    } catch (error) {
      hilog.error(0x0000, 'DataSyncManager', `Save record failed: ${JSON.stringify(error)}`);
    }
  }
  public async syncToCloud(): Promise<void> {
    if (!this.kvStore || this.ocppManager.getState() !== OcppState.REGISTERED) return;
    try {
      const queueData = await this.kvStore.get(this.SYNC_QUEUE_KEY, '[]');
      let queue: string[] = JSON.parse(queueData as string);
      if (queue.length === 0) return;
      hilog.info(0x0000, 'DataSyncManager', `Syncing ${queue.length} records...`);
      const failedQueue: string[] = [];
      for (const sessionId of queue) {
        const recordData = await this.kvStore.get(sessionId);
        if (recordData) {
          const record: ChargingRecord = JSON.parse(recordData as string);
          try {
            // Assume OcppManager has a method to send data
            // this.ocppManager.sendMeterValues(record); 
            hilog.info(0x0000, 'DataSyncManager', `Successfully synced record ${sessionId}`);
            record.synced = true;
            await this.kvStore.put(sessionId, JSON.stringify(record));
          } catch (sendError) {
            hilog.error(0x0000, 'DataSyncManager', `Sync failed for ${sessionId}: ${JSON.stringify(sendError)}`);
            failedQueue.push(sessionId);
          }
        }
      }
      await this.kvStore.put(this.SYNC_QUEUE_KEY, JSON.stringify(failedQueue));
    } catch (error) {
      hilog.error(0x0000, 'DataSyncManager', `Sync process error: ${JSON.stringify(error)}`);
    }
  }
}
3.3.4 讲解
  • 依赖注入 :构造函数接收 contextocppManager,降低了模块间的耦合度。
  • 本地优先saveRecord 总是先写入本地数据库,并更新同步队列,确保数据不丢失。
  • 自动同步setupNetworkListener 监听网络状态,一旦网络可用,自动触发 syncToCloud

4. 模块整合与启动流程

entry/src/main/ets/entryability/EntryAbility.ets

typescript 复制代码
// entry/src/main/ets/entryability/EntryAbility.ets
import { UIAbility, AbilityConstant, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { OcppManager } from '../model/OcppManager';
import { MeteringService } from '../model/MeteringService';
import { DataSyncManager } from '../model/DataSyncManager';
// 全局服务实例,便于其他模块访问
export let globalOcppManager: OcppManager | null = null;
export let globalMeteringService: MeteringService | null = null;
export let globalDataSyncManager: DataSyncManager | null = null;
export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'EntryAbility', 'Ability onCreate');
    
    // 1. 初始化核心服务
    // OCPP Manager 不依赖其他服务,最先创建
    globalOcppManager = new OcppManager();
    // Metering Service 依赖 Context
    globalMeteringService = new MeteringService(this.context);
    // Data Sync Manager 依赖 Context 和 Ocpp Manager
    globalDataSyncManager = new DataSyncManager(this.context, globalOcppManager);
    
    hilog.info(0x0000, 'EntryAbility', 'All services initialized.');
  }
  onDestroy(): void {
    hilog.info(0x0000, 'EntryAbility', 'Ability onDestroy');
    // 可以在这里执行资源清理
  }
  onWindowStageCreate(windowStage: window.WindowStage): void {
    hilog.info(0x0000, 'EntryAbility', 'Ability onWindowStageCreate');
    windowStage.loadContent('pages/Index', (err, data) => {
      if (err.code) {
        hilog.error(0x0000, 'EntryAbility', 'Failed to load content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
    });
  }
}

整合讲解:

  1. 服务容器EntryAbility 作为应用入口,充当了简单的服务容器。它负责按依赖顺序创建所有核心服务实例。
  2. 依赖管理
    • OcppManager 是独立的,首先创建。
    • MeteringService 需要 context 来访问文件系统,因此传入 this.context
    • DataSyncManager 需要 contextOcppManager 实例,因此将两者都传入。
  3. 全局访问 :通过导出全局变量,使得应用内的其他页面或组件可以方便地访问这些服务实例(例如,UI 页面可以调用 globalOcppManager.startTransaction())。在更复杂的应用中,可以考虑使用更高级的依赖注入框架。

待做:

  • UI/UX 完善:开发丰富的用户交互界面,如扫码充电、支付、账单展示等。
  • OCPP 2.0.1 深度集成:实现 ISO 15118 插充通信、智能充电等高级特性。
  • 分布式能力扩展:探索与手机、智能家居等设备的分布式协同,打造智慧充电生态。
相关推荐
盐焗西兰花8 小时前
鸿蒙学习实战之路 - 图片预览功能实现
学习·华为·harmonyos
盐焗西兰花8 小时前
鸿蒙学习实战之路:HarmonyOS 布局性能优化最佳实践
华为·性能优化·harmonyos
xiaocao_102315 小时前
鸿蒙手机上使用的备忘录怎么导出来?
华为·智能手机·harmonyos
春卷同学17 小时前
打砖块 - Electron for 鸿蒙PC项目实战案例
android·electron·harmonyos
春卷同学20 小时前
Electron for鸿蒙PC开发的骰子游戏应用
游戏·electron·harmonyos
鹧鸪云光伏20 小时前
智绘光储新蓝图 鹧鸪云领航能源转型
能源
春卷同学20 小时前
Electron for 鸿蒙pc开发的二十一点游戏
游戏·electron·harmonyos
不老刘21 小时前
react native for OpenHarmony iconfont 图标不显示问题
react native·harmonyos·iconfont
kirk_wang21 小时前
Flutter三方库鸿蒙适配深度解析:从架构原理到性能优化实践
flutter·移动开发·跨平台·arkts·鸿蒙
汉堡黄•᷄ࡇ•᷅21 小时前
鸿蒙开发: 案例集合List:ListItem侧滑(删除、收藏)
harmonyos·鸿蒙·鸿蒙系统