Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/app/core/device-v2/device-v2-error.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, expect, it } from 'vitest';

import { DeviceV2RouteError } from '../protocol/device-v2/session';
import { Bbp2ErrorCode } from '../protocol/device-v2/types';
import { deviceV2ErrorMessage } from './device-v2-error';

describe('deviceV2ErrorMessage', () => {
it('keeps the wire error and appends an actionable Chinese explanation', () => {
const error = new DeviceV2RouteError(Bbp2ErrorCode.UnknownEndpoint);

expect(deviceV2ErrorMessage(error, '设备同步失败')).toBe(
'Device V2 route failed with wire error 5:未获取到设备能力,或设备当前未上线。',
);
});

it('recognizes a serialized wire error message', () => {
const error = new Error('Device V2 route failed with wire error 12');

expect(deviceV2ErrorMessage(error, '指令发送失败')).toBe(
'Device V2 route failed with wire error 12:操作过于频繁,请稍后再试。',
);
});

it('keeps useful non-protocol error messages unchanged', () => {
expect(deviceV2ErrorMessage(new Error('connection lost'), '设备同步失败'))
.toBe('connection lost');
});

it('uses the caller fallback for an unknown failure', () => {
expect(deviceV2ErrorMessage(undefined, '设备同步失败')).toBe('设备同步失败');
});
});
37 changes: 37 additions & 0 deletions src/app/core/device-v2/device-v2-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { DeviceV2RouteError } from '../protocol/device-v2/session';
import { Bbp2ErrorCode } from '../protocol/device-v2/types';

const routeErrorExplanations: Partial<Record<Bbp2ErrorCode, string>> = {
[Bbp2ErrorCode.MalformedMessage]: '设备无法识别本次指令,请更新设备固件后重试。',
[Bbp2ErrorCode.AuthenticationRequired]: '设备连接鉴权已失效,请重新连接。',
[Bbp2ErrorCode.NegotiationRequired]: '设备能力尚未同步,请稍后重试。',
[Bbp2ErrorCode.UnsupportedMessage]: '当前设备不支持此操作。',
[Bbp2ErrorCode.UnknownEndpoint]: '未获取到设备能力,或设备当前未上线。',
[Bbp2ErrorCode.CommandRejected]: '设备拒绝执行本次操作。',
[Bbp2ErrorCode.ResourceExhausted]: '设备正忙,请稍后重试。',
[Bbp2ErrorCode.Internal]: '设备通信发生内部错误,请稍后重试。',
[Bbp2ErrorCode.SequenceConflict]: '设备数据正在同步,请稍后重试。',
[Bbp2ErrorCode.StateConflict]: '设备状态已变化,请刷新后重试。',
[Bbp2ErrorCode.ManifestConflict]: '设备能力已变化,正在重新同步。',
[Bbp2ErrorCode.RateLimited]: '操作过于频繁,请稍后再试。',
};

const wireErrorPattern = /Device V2 route failed with wire error (\d+)/;

export function deviceV2ErrorMessage(error: unknown, fallback: string): string {
const original = error instanceof Error && error.message ? error.message : fallback;
const routeCode = deviceV2RouteErrorCode(error, original);
const explanation = routeCode === undefined ? undefined : routeErrorExplanations[routeCode];

return explanation ? `${original}:${explanation}` : original;
}

function deviceV2RouteErrorCode(error: unknown, message: string): Bbp2ErrorCode | undefined {
if (error instanceof DeviceV2RouteError) return error.code;

const match = wireErrorPattern.exec(message);
if (!match) return undefined;

const code = Number(match[1]);
return Number.isInteger(code) ? code as Bbp2ErrorCode : undefined;
}
14 changes: 7 additions & 7 deletions src/app/device/v2/device-v2.page.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,6 @@

<ion-content [fullscreen]="true">
<main class="v2-content content-under-header">
@if (error) {
<section class="notice error">
<span>{{ error }}</span>
<ion-button size="small" fill="clear" (click)="retry()">重试</ion-button>
</section>
}

@if (telemetryError) {
<section class="notice">
<span>实时数据已降级:{{ telemetryError }}</span>
Expand Down Expand Up @@ -127,5 +120,12 @@
</section>
}
}

@if (error) {
<section class="notice error" role="alert" aria-live="assertive">
<span>{{ error }}</span>
<ion-button size="small" fill="clear" (click)="retry()">重试</ion-button>
</section>
}
</main>
</ion-content>
17 changes: 17 additions & 0 deletions src/app/device/v2/device-v2.page.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ ion-content {

.v2-content {
box-sizing: border-box;
display: flex;
flex-direction: column;
max-width: 720px;
min-height: 100%;
margin: 0 auto;
Expand Down Expand Up @@ -56,11 +58,26 @@ ion-content {
gap: 12px;
}

.notice {
> span {
min-width: 0;
overflow-wrap: anywhere;
}

ion-button {
flex: 0 0 auto;
}
}

.notice.error,
.field-error {
color: var(--ion-color-danger);
}

.notice.error {
margin-top: auto;
}

.summary {
color: var(--blinker-text-secondary);
font-size: 12px;
Expand Down
3 changes: 2 additions & 1 deletion src/app/device/v2/device-v2.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
DeviceUiTelemetryLease,
DeviceUiValue,
} from '../../core/device-v2/device-ui.port';
import { deviceV2ErrorMessage } from '../../core/device-v2/device-v2-error';
import {
diffPageLayout,
generateDefaultPageLayout,
Expand Down Expand Up @@ -568,7 +569,7 @@ export class DeviceV2Page implements OnInit, OnChanges, OnDestroy {
}

private messageOf(error: unknown, fallback: string): string {
return error instanceof Error && error.message ? error.message : fallback;
return deviceV2ErrorMessage(error, fallback);
}

private errorCode(error: unknown): string {
Expand Down