基于 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 插充通信、智能充电等高级特性。
  • 分布式能力扩展:探索与手机、智能家居等设备的分布式协同,打造智慧充电生态。
相关推荐
电子小子洋酱4 小时前
BearPi小熊派 鸿蒙入门开发笔记(4)
笔记·华为·harmonyos
熊猫钓鱼>_>5 小时前
【案例实战】鸿蒙分布式智能办公应用的架构设计与性能优化
分布式·华为·harmonyos
鸿蒙小白龙6 小时前
Openharmony应用开发之Ability异常退出与UIAbility数据备份开发实战
harmonyos·鸿蒙·鸿蒙系统·open harmony
Damon小智6 小时前
RedPlayer 视频播放器在 HarmonyOS 应用中的实践
音视频·harmonyos·鸿蒙·小红书·三方库·redplayer
猫林老师18 小时前
HarmonyOS分布式硬件共享:调用手机摄像头的手表应用
华为·交互·harmonyos
前端世界1 天前
HarmonyOS应用开发指南:Toast无法显示的完整排查流程与实战案例
华为·harmonyos
安卓开发者1 天前
鸿蒙NEXT Wear Engine穿戴侧应用开发完全指南
ubuntu·华为·harmonyos
安卓开发者1 天前
鸿蒙Next振动开发指南:打造沉浸式触觉反馈体验
华为·harmonyos
Devil枫1 天前
HarmonyOS屏幕方向适配指南
华为·harmonyos