鸿蒙跨端家庭能耗监测系统开发指南
一、系统架构设计
基于HarmonyOS的智能家居和分布式技术,构建家庭能耗监测系统:
- 数据采集层:通过智能家居接口获取用电数据
- 数据处理层:分析能耗数据并生成统计信息
- 可视化层:展示实时和历史能耗数据
- 跨端同步层:多设备间同步能耗数据和告警信息
https://example.com/harmony-energy-monitor-arch.png
二、核心代码实现
1. 能耗监测服务
// EnergyService.ets
import smartHome from '@ohos.smartHome.energy';
import distributedData from '@ohos.distributedData';
import { EnergyData, DeviceConsumption, EnergyAlert } from './EnergyTypes';
class EnergyService {
private static instance: EnergyService = null;
private energyManager: smartHome.EnergyManager;
private dataManager: distributedData.DataManager;
private listeners: EnergyListener[] = [];
private currentConsumption: Record<string, DeviceConsumption> = {};
private alerts: EnergyAlert[] = [];
private constructor() {
this.initEnergyManager();
this.initDataManager();
}
public static getInstance(): EnergyService {
if (!EnergyService.instance) {
EnergyService.instance = new EnergyService();
}
return EnergyService.instance;
}
private initEnergyManager(): void {
try {
this.energyManager = smartHome.createEnergyManager(getContext());
// 注册能耗数据监听
this.energyManager.on('consumptionChange', (data) => {
this.handleConsumptionData(data);
});
// 启动实时监测
this.energyManager.startMonitoring({
interval: 5000, // 5秒更新一次
callback: (err, data) => {
if (err) {
console.error('能耗监测错误:', JSON.stringify(err));
}
}
});
} catch (err) {
console.error('初始化能耗管理器失败:', JSON.stringify(err));
}
}
private initDataManager(): void {
this.dataManager = distributedData.createDataManager({
bundleName: 'com.example.energy',
area: distributedData.Area.GLOBAL,
isEncrypted: true
});
this.dataManager.registerDataListener('energy_sync', (data) => {
this.handleSyncData(data);
});
}
public async requestPermissions(): Promise<boolean> {
try {
const permissions = [
'ohos.permission.SMART_HOME_ENERGY',
'ohos.permission.DISTRIBUTED_DATASYNC'
];
const result = await abilityAccessCtrl.requestPermissionsFromUser(
getContext(),
permissions
);
return result.grantedPermissions.length === permissions.length;
} catch (err) {
console.error('请求权限失败:', JSON.stringify(err));
return false;
}
}
public async getDeviceList(): Promise<smartHome.DeviceInfo[]> {
try {
return await this.energyManager.getDevices();
} catch (err) {
console.error('获取设备列表失败:', JSON.stringify(err));
throw err;
}
}
public async getCurrentConsumption(): Promise<Record<string, DeviceConsumption>> {
try {
const devices = await this.getDeviceList();
const consumptionData: Record<string, DeviceConsumption> = {};
for (const device of devices) {
const data = await this.energyManager.getDeviceConsumption(device.deviceId);
consumptionData[device.deviceId] = {
deviceId: device.deviceId,
deviceName: device.deviceName,
power: data.power,
current: data.current,
voltage: data.voltage,
timestamp: Date.now()
};
}
this.currentConsumption = consumptionData;
return consumptionData;
} catch (err) {
console.error('获取当前能耗失败:', JSON.stringify(err));
throw err;
}
}
public async getHistoryConsumption(period: 'day' | 'week' | 'month'): Promise<EnergyData[]> {
try {
const endTime = Date.now();
let startTime = endTime;
switch (period) {
case 'day':
startTime = endTime - 24 * 60 * 60 * 1000;
break;
case 'week':
startTime = endTime - 7 * 24 * 60 * 60 * 1000;
break;
case 'month':
startTime = endTime - 30 * 24 * 60 * 60 * 1000;
break;
}
return await this.energyManager.queryHistory({
startTime,
endTime,
interval: period === 'day' ? 'hour' : 'day'
});
} catch (err) {
console.error('获取历史数据失败:', JSON.stringify(err));
throw err;
}
}
public async setAlert(deviceId: string, threshold: number): Promise<EnergyAlert> {
try {
const alert: EnergyAlert = {
id: `${deviceId}_${Date.now()}`,
deviceId,
threshold,
isActive: true,
createdAt: Date.now()
};
this.alerts = [...this.alerts, alert];
this.syncAlert(alert);
return alert;
} catch (err) {
console.error('设置告警失败:', JSON.stringify(err));
throw err;
}
}
private handleConsumptionData(data: smartHome.ConsumptionData): void {
const deviceId = data.deviceId;
const consumption: DeviceConsumption = {
deviceId,
deviceName: data.deviceName || `设备_${deviceId.slice(-4)}`,
power: data.power,
current: data.current,
voltage: data.voltage,
timestamp: Date.now()
};
this.currentConsumption[deviceId] = consumption;
this.notifyConsumptionUpdated(consumption);
this.syncConsumption(consumption);
// 检查告警
this.checkAlerts(consumption);
}
private checkAlerts(consumption: DeviceConsumption): void {
const deviceAlerts = this.alerts.filter(
alert => alert.deviceId === consumption.deviceId && alert.isActive
);
deviceAlerts.forEach(alert => {
if (consumption.power >= alert.threshold) {
this.triggerAlert(alert, consumption);
}
});
}
private triggerAlert(alert: EnergyAlert, consumption: DeviceConsumption): void {
const notification: notification.NotificationRequest = {
id: parseInt(alert.id),
content: {
title: '能耗告警',
text: `${consumption.deviceName} 能耗超过阈值 ${alert.threshold}W`,
additionalText: `当前功率: ${consumption.power.toFixed(2)}W`,
tapAction: {
bundleName: 'com.example.energy',
abilityName: 'MainAbility'
}
}
};
notification.publish(notification).catch(err => {
console.error('发送通知失败:', JSON.stringify(err));
});
this.notifyAlertTriggered(alert, consumption);
}
private syncConsumption(consumption: DeviceConsumption): void {
this.dataManager.syncData('consumption_sync', {
type: 'consumption_update',
data: consumption,
timestamp: Date.now()
});
}
private syncAlert(alert: EnergyAlert): void {
this.dataManager.syncData('alert_sync', {
type: 'alert_set',
data: alert,
timestamp: Date.now()
});
}
private handleSyncData(data: any): void {
if (!data) return;
switch (data.type) {
case 'consumption_update':
this.handleConsumptionUpdate(data.data);
break;
case 'alert_set':
this.handleAlertSet(data.data);
break;
case 'alert_triggered':
this.handleAlertTriggered(data.data);
break;
}
}
private handleConsumptionUpdate(consumption: DeviceConsumption): void {
this.currentConsumption[consumption.deviceId] = consumption;
this.notifyConsumptionUpdated(consumption);
}
private handleAlertSet(alert: EnergyAlert): void {
const existingIndex = this.alerts.findIndex(a => a.id === alert.id);
if (existingIndex >= 0) {
this.alerts[existingIndex] = alert;
} else {
this.alerts = [...this.alerts, alert];
}
this.notifyAlertSet(alert);
}
private handleAlertTriggered(data: { alert: EnergyAlert, consumption: DeviceConsumption }): void {
this.notifyAlertTriggered(data.alert, data.consumption);
}
private notifyConsumptionUpdated(consumption: DeviceConsumption): void {
this.listeners.forEach(listener => {
listener.onConsumptionUpdated?.(consumption);
});
}
private notifyAlertSet(alert: EnergyAlert): void {
this.listeners.forEach(listener => {
listener.onAlertSet?.(alert);
});
}
private notifyAlertTriggered(alert: EnergyAlert, consumption: DeviceConsumption): void {
this.listeners.forEach(listener => {
listener.onAlertTriggered?.(alert, consumption);
});
}
public addListener(listener: EnergyListener): void {
if (!this.listeners.includes(listener)) {
this.listeners.push(listener);
}
}
public removeListener(listener: EnergyListener): void {
this.listeners = this.listeners.filter(l => l !== listener);
}
}
interface EnergyListener {
onConsumptionUpdated?(consumption: DeviceConsumption): void;
onAlertSet?(alert: EnergyAlert): void;
onAlertTriggered?(alert: EnergyAlert, consumption: DeviceConsumption): void;
}
export const energyService = EnergyService.getInstance();
2. 主界面实现
// MainScreen.ets
import { energyService } from './EnergyService';
import { DeviceConsumption, EnergyData, EnergyAlert } from './EnergyTypes';
@Component
export struct MainScreen {
@State hasPermission: boolean = false;
@State currentConsumption: Record<string, DeviceConsumption> = {};
@State historyData: EnergyData[] = [];
@State selectedPeriod: 'day' | 'week' | 'month' = 'day';
@State showAlertDialog: boolean = false;
@State newAlertThreshold: number = 1000;
@State selectedDevice: string = '';
@State alerts: EnergyAlert[] = [];
build() {
Column() {
// 标题栏
Row() {
Text('家庭能耗监测')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Button(this.hasPermission ? '设置告警' : '授权')
.width(100)
.onClick(() => {
if (this.hasPermission) {
this.showAlertDialog = true;
} else {
this.requestPermissions();
}
})
}
.padding(10)
.width('100%')
// 实时能耗卡片
Column() {
Text('实时能耗')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 10 })
if (Object.keys(this.currentConsumption).length === 0) {
Text('无能耗数据')
.fontSize(16)
.fontColor('#666666')
} else {
Grid() {
ForEach(Object.values(this.currentConsumption), (consumption) => {
GridItem() {
Column() {
Text(consumption.deviceName)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 5 })
Row() {
Text(`${consumption.power.toFixed(2)}`)
.fontSize(24)
.fontColor('#FF5252')
Text('W')
.fontSize(16)
.fontColor('#666666')
.margin({ left: 5, top: 10 })
}
Row() {
Text(`${consumption.voltage.toFixed(1)}V`)
.fontSize(14)
.fontColor('#666666')
.margin({ right: 10 })
Text(`${consumption.current.toFixed(2)}A`)
.fontSize(14)
.fontColor('#666666')
}
.margin({ top: 5 })
}
.padding(10)
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius(8)
.onClick(() => {
this.selectedDevice = consumption.deviceId;
this.showAlertDialog = true;
})
}
})
}
.columnsTemplate('1fr 1fr')
.rowsGap(10)
.columnsGap(10)
}
}
.padding(15)
.width('100%')
.backgroundColor('#F5F5F5')
.borderRadius(8)
.margin({ bottom: 20 })
// 历史能耗图表
Column() {
Row() {
Text('历史能耗')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Segmented({
index: this.getPeriodIndex(),
items: ['日', '周', '月'],
onSelect: (index: number) => {
this.changePeriod(index);
}
})
.width(200)
}
.margin({ bottom: 10 })
if (this.historyData.length === 0) {
Column() {
Text('无历史数据')
.fontSize(16)
.margin({ bottom: 5 })
Text('选择时间段查看历史能耗')
.fontSize(14)
.fontColor('#666666')
}
.padding(20)
.width('90%')
.backgroundColor('#FFFFFF')
.borderRadius(8)
.margin({ top: 20 })
} else {
EnergyChart({
data: this.historyData,
period: this.selectedPeriod
})
.height(250)
}
}
.padding(15)
.width('100%')
.backgroundColor('#F5F5F5')
.borderRadius(8)
.margin({ bottom: 20 })
// 告警列表
Column() {
Text('能耗告警')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 10 })
if (this.alerts.length === 0) {
Text('无告警设置')
.fontSize(16)
.fontColor('#666666')
} else {
List({ space: 10 }) {
ForEach(this.alerts, (alert) => {
ListItem() {
Row() {
Column() {
Text(this.getDeviceName(alert.deviceId))
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 5 })
Text(`阈值: ${alert.threshold}W`)
.fontSize(14)
.fontColor('#666666')
}
.layoutWeight(1)
Toggle({
type: ToggleType.Switch,
isOn: alert.isActive,
onChange: (isOn) => {
this.toggleAlert(alert.id, isOn);
}
})
}
.padding(10)
.width('100%')
}
})
}
.height(150)
}
}
.padding(15)
.width('100%')
.backgroundColor('#F5F5F5')
.borderRadius(8)
}
.width('100%')
.height('100%')
.padding(20)
// 设置告警对话框
if (this.showAlertDialog) {
DialogComponent({
title: '设置能耗告警',
content: this.buildAlertDialogContent(),
confirm: {
value: '设置',
action: () => this.setAlert()
},
cancel: {
value: '取消',
action: () => {
this.showAlertDialog = false;
this.newAlertThreshold = 1000;