|
|
|
|
@ -1,4 +1,4 @@
|
|
|
|
|
import { M9zService, TaskProgram, TaskInstruction, M9zCmd, ControlTask, TaskLoop } from '../../utils/M9zService';
|
|
|
|
|
import { M9zService, TaskProgram, TaskInstruction, M9zCmd, ControlTask, TaskLoop, SunRiseTimeData } from '../../utils/M9zService';
|
|
|
|
|
import { promptAction } from '@kit.ArkUI';
|
|
|
|
|
|
|
|
|
|
@Observed
|
|
|
|
|
@ -16,6 +16,8 @@ export struct AutoMode {
|
|
|
|
|
@State startTask: ControlTask = this.createEmptyTask();
|
|
|
|
|
@State stopTask: ControlTask = this.createEmptyTask();
|
|
|
|
|
@State middleTasks: ControlTask[] = [];
|
|
|
|
|
@State sunriseTime: string = '--:--:--';
|
|
|
|
|
@State sunsetTime: string = '--:--:--';
|
|
|
|
|
|
|
|
|
|
@State isLoading: boolean = false;
|
|
|
|
|
@State isSetting: boolean = false;
|
|
|
|
|
@ -117,6 +119,26 @@ export struct AutoMode {
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fetchSunRiseTime() {
|
|
|
|
|
this.m9zService.getSunRiseTime({
|
|
|
|
|
onSuccess: (data: SunRiseTimeData): void => {
|
|
|
|
|
if (data.sunrise) this.sunriseTime = this.formatTimeOnly(data.sunrise);
|
|
|
|
|
if (data.sunset) this.sunsetTime = this.formatTimeOnly(data.sunset);
|
|
|
|
|
this.isLoading = false;
|
|
|
|
|
},
|
|
|
|
|
onError: (err: string): void => {
|
|
|
|
|
this.isLoading = false;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
formatTimeOnly(date: Date): string {
|
|
|
|
|
const h = String(date.getHours()).padStart(2, '0');
|
|
|
|
|
const min = String(date.getMinutes()).padStart(2, '0');
|
|
|
|
|
const s = String(date.getSeconds()).padStart(2, '0');
|
|
|
|
|
return `${h}:${min}:${s}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
changeViewMode(index: number) {
|
|
|
|
|
this.selectedViewMode = index;
|
|
|
|
|
@ -141,12 +163,29 @@ export struct AutoMode {
|
|
|
|
|
const instructions: TaskInstruction[] = [];
|
|
|
|
|
|
|
|
|
|
// 1. Time Instruction
|
|
|
|
|
const parts: string[] = task.execTime.split(':');
|
|
|
|
|
if (parts.length >= 2) {
|
|
|
|
|
const h: number = Number(parts[0]);
|
|
|
|
|
const m: number = Number(parts[1]);
|
|
|
|
|
const minutes: number = h * 60 + m;
|
|
|
|
|
|
|
|
|
|
let h = 0;
|
|
|
|
|
let m = 0;
|
|
|
|
|
|
|
|
|
|
if (task.execTime.includes(' ')) {
|
|
|
|
|
// Format: YYYY-MM-DD HH:mm:ss
|
|
|
|
|
const timePart = task.execTime.split(' ')[1];
|
|
|
|
|
const parts = timePart.split(':');
|
|
|
|
|
if (parts.length >= 2) {
|
|
|
|
|
h = Number(parts[0]);
|
|
|
|
|
m = Number(parts[1]);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Format: HH:mm (or HH:mm:ss)
|
|
|
|
|
const parts = task.execTime.split(':');
|
|
|
|
|
if (parts.length >= 2) {
|
|
|
|
|
h = Number(parts[0]);
|
|
|
|
|
m = Number(parts[1]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const minutes: number = h * 60 + m;
|
|
|
|
|
|
|
|
|
|
if (true) { // Always generate instruction if we have time
|
|
|
|
|
const ins1: TaskInstruction = new TaskInstruction();
|
|
|
|
|
ins1.type = 0x01;
|
|
|
|
|
ins1.param = task.timeType === 0 ? 0x01 : 0x02;
|
|
|
|
|
@ -455,59 +494,194 @@ export struct AutoMode {
|
|
|
|
|
.width('100%')
|
|
|
|
|
|
|
|
|
|
// Control Panel
|
|
|
|
|
Row() {
|
|
|
|
|
Column() {
|
|
|
|
|
// Current Mode Display
|
|
|
|
|
Column() {
|
|
|
|
|
Text('⚙️ 当前运行模式')
|
|
|
|
|
.fontColor('#b2ebf2')
|
|
|
|
|
.fontSize(14)
|
|
|
|
|
.width('100%')
|
|
|
|
|
.margin({ bottom: 8 })
|
|
|
|
|
|
|
|
|
|
Row() {
|
|
|
|
|
Text(`自动模式${this.currentMode + 1}`)
|
|
|
|
|
.fontColor('#22fdc8')
|
|
|
|
|
.fontSize(16)
|
|
|
|
|
.fontWeight(FontWeight.Bold)
|
|
|
|
|
}
|
|
|
|
|
.width('100%')
|
|
|
|
|
.padding(12)
|
|
|
|
|
.backgroundColor('rgba(34, 253, 200, 0.1)')
|
|
|
|
|
.borderRadius(8)
|
|
|
|
|
.border({ width: 1, color: '#22fdc8' })
|
|
|
|
|
}
|
|
|
|
|
.width('100%')
|
|
|
|
|
.padding(10)
|
|
|
|
|
.backgroundColor('rgba(15, 30, 60, 0.4)')
|
|
|
|
|
.borderRadius(8)
|
|
|
|
|
.margin({ bottom: 15 })
|
|
|
|
|
|
|
|
|
|
Button(`应用模式${this.selectedViewMode+1}到设备`)
|
|
|
|
|
.onClick((): void => {this.changeDeviceMode();})
|
|
|
|
|
.width('80%')
|
|
|
|
|
.backgroundColor('#22fdc8')
|
|
|
|
|
.fontColor('#000')
|
|
|
|
|
.width('100%')
|
|
|
|
|
.backgroundColor('#102a45')
|
|
|
|
|
.fontColor('#22fdc8')
|
|
|
|
|
.border({ width: 1, color: '#22fdc8' })
|
|
|
|
|
.height(44)
|
|
|
|
|
}
|
|
|
|
|
.width('100%')
|
|
|
|
|
.justifyContent(FlexAlign.Center)
|
|
|
|
|
.padding(10)
|
|
|
|
|
|
|
|
|
|
// Bottom Info
|
|
|
|
|
Column() {
|
|
|
|
|
Row() {
|
|
|
|
|
Text(`日出时间:${this.sunriseTime}`).fontSize(12).fontColor('#b2ebf2').layoutWeight(1)
|
|
|
|
|
Text(`日落时间:${this.sunsetTime}`).fontSize(12).fontColor('#b2ebf2').layoutWeight(1)
|
|
|
|
|
}.width('100%')
|
|
|
|
|
}
|
|
|
|
|
.width('100%')
|
|
|
|
|
.padding(15)
|
|
|
|
|
.backgroundColor('rgba(15, 30, 60, 0.4)')
|
|
|
|
|
.borderRadius(8)
|
|
|
|
|
.margin({bottom: 20})
|
|
|
|
|
|
|
|
|
|
// Dialog
|
|
|
|
|
if(this.showTaskDialog) {
|
|
|
|
|
Stack() {
|
|
|
|
|
// Mask
|
|
|
|
|
Rect().width('100%').height('100%').fill('rgba(0,0,0,0.8)').onClick((): void => {this.showTaskDialog = false;})
|
|
|
|
|
|
|
|
|
|
// Content
|
|
|
|
|
Column() {
|
|
|
|
|
Text('编辑任务').fontColor('#fff').fontSize(16).margin({bottom: 10})
|
|
|
|
|
|
|
|
|
|
// Loops
|
|
|
|
|
Text('选择开启回路').fontColor('#b2ebf2').margin({bottom: 5}).alignSelf(ItemAlign.Start)
|
|
|
|
|
Grid() {
|
|
|
|
|
ForEach([0,1,2,3,4,5,6,7,8,9], (idx: number) => {
|
|
|
|
|
GridItem() {
|
|
|
|
|
Text(`${idx+1}`)
|
|
|
|
|
.backgroundColor(this.editingLoops.includes(idx) ? '#22fdc8' : '#333')
|
|
|
|
|
.fontColor(this.editingLoops.includes(idx) ? '#000' : '#fff')
|
|
|
|
|
.width(30).height(30).borderRadius(15)
|
|
|
|
|
.textAlign(TextAlign.Center)
|
|
|
|
|
.onClick((): void => {this.toggleEditingLoop(idx);})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}.columnsTemplate('1fr 1fr 1fr 1fr 1fr').height(80)
|
|
|
|
|
Text('编辑任务').fontColor('#fff').fontSize(16).margin({bottom: 10}).fontWeight(FontWeight.Bold)
|
|
|
|
|
|
|
|
|
|
// Time
|
|
|
|
|
Row() {
|
|
|
|
|
Text('时间').fontColor('#b2ebf2')
|
|
|
|
|
TextInput({ text: this.editingTask.execTime }).width(100).backgroundColor('#333').fontColor('#fff')
|
|
|
|
|
.onChange((val: string): void => {this.editingTask.execTime = val;})
|
|
|
|
|
}.width('100%').margin({ top: 10 }).justifyContent(FlexAlign.SpaceBetween)
|
|
|
|
|
Scroll() {
|
|
|
|
|
Column() {
|
|
|
|
|
// 1. Time Type
|
|
|
|
|
Text('时间类型').fontColor('#b2ebf2').margin({top: 10, bottom: 5}).alignSelf(ItemAlign.Start).fontSize(14)
|
|
|
|
|
Row() {
|
|
|
|
|
Button('定时时间')
|
|
|
|
|
.backgroundColor(this.editingTask.timeType === 0 ? '#22fdc8' : '#333')
|
|
|
|
|
.fontColor(this.editingTask.timeType === 0 ? '#000' : '#fff')
|
|
|
|
|
.onClick(() => { this.editingTask.timeType = 0; })
|
|
|
|
|
.height(30).fontSize(12).margin({right: 10})
|
|
|
|
|
|
|
|
|
|
Button('经纬度时间')
|
|
|
|
|
.backgroundColor(this.editingTask.timeType === 1 ? '#22fdc8' : '#333')
|
|
|
|
|
.fontColor(this.editingTask.timeType === 1 ? '#000' : '#fff')
|
|
|
|
|
.onClick(() => { this.editingTask.timeType = 1; })
|
|
|
|
|
.height(30).fontSize(12)
|
|
|
|
|
}.width('100%').justifyContent(FlexAlign.Start)
|
|
|
|
|
|
|
|
|
|
// 2. Time Input
|
|
|
|
|
if (this.editingTask.timeType === 0) {
|
|
|
|
|
Text('执行时间').fontColor('#b2ebf2').margin({top: 15, bottom: 5}).alignSelf(ItemAlign.Start).fontSize(14)
|
|
|
|
|
Row() {
|
|
|
|
|
TextInput({ text: this.editingTask.execTime.split(' ')[1] ? this.editingTask.execTime.split(' ')[1].substring(0,5) : '00:00' })
|
|
|
|
|
.width(120).height(40)
|
|
|
|
|
.backgroundColor('#333').fontColor('#fff')
|
|
|
|
|
.borderRadius(5)
|
|
|
|
|
.onChange((val: string) => {
|
|
|
|
|
// Simple validation/update. better to use TimePicker but native TimePicker takes much space.
|
|
|
|
|
// Assume user types HH:mm. we append to today's date prefix on save or keep full string.
|
|
|
|
|
// Reference Vue: uses picker. Here let's just use text input for simplicity or check if we can use TimePickerDialog
|
|
|
|
|
// For now update logic:
|
|
|
|
|
if (val.length === 5) {
|
|
|
|
|
const today = new Date();
|
|
|
|
|
const y = today.getFullYear();
|
|
|
|
|
const m = String(today.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
const d = String(today.getDate()).padStart(2, '0');
|
|
|
|
|
this.editingTask.execTime = `${y}-${m}-${d} ${val}:00`;
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
Button('选择时间')
|
|
|
|
|
.onClick(() => {
|
|
|
|
|
try {
|
|
|
|
|
TimePickerDialog.show({
|
|
|
|
|
useMilitaryTime: true,
|
|
|
|
|
onAccept: (value: TimePickerResult) => {
|
|
|
|
|
const today = new Date();
|
|
|
|
|
const y = today.getFullYear();
|
|
|
|
|
const m = String(today.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
const d = String(today.getDate()).padStart(2, '0');
|
|
|
|
|
const h = String(value.hour).padStart(2, '0');
|
|
|
|
|
const min = String(value.minute).padStart(2, '0');
|
|
|
|
|
this.editingTask.execTime = `${y}-${m}-${d} ${h}:${min}:00`;
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
} catch(e) {
|
|
|
|
|
console.error('TimePicker error', e);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.margin({left: 10}).fontSize(12).height(30).backgroundColor('#2d3a51')
|
|
|
|
|
.fontColor('#22fdc8').border({width:1, color: '#22fdc8'})
|
|
|
|
|
}.width('100%').justifyContent(FlexAlign.Start)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. Loop Selection
|
|
|
|
|
Text('选择开启回路').fontColor('#b2ebf2').margin({top: 15, bottom: 5}).alignSelf(ItemAlign.Start).fontSize(14)
|
|
|
|
|
Grid() {
|
|
|
|
|
ForEach([0,1,2,3,4,5,6,7,8,9], (idx: number) => {
|
|
|
|
|
GridItem() {
|
|
|
|
|
Text(`${idx+1}`)
|
|
|
|
|
.backgroundColor(this.editingLoops.includes(idx) ? '#22fdc8' : '#333')
|
|
|
|
|
.fontColor(this.editingLoops.includes(idx) ? '#000' : '#fff')
|
|
|
|
|
.width(30).height(30).borderRadius(15)
|
|
|
|
|
.textAlign(TextAlign.Center)
|
|
|
|
|
.onClick((): void => {this.toggleEditingLoop(idx);})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}.columnsTemplate('1fr 1fr 1fr 1fr 1fr').height(80)
|
|
|
|
|
|
|
|
|
|
// 4. Brightness Settings for Selected Loops
|
|
|
|
|
if (this.editingLoops.length > 0) {
|
|
|
|
|
Text('调光设置').fontColor('#b2ebf2').margin({top: 15, bottom: 5}).alignSelf(ItemAlign.Start).fontSize(14)
|
|
|
|
|
List() {
|
|
|
|
|
ForEach(this.editingLoops, (loopIdx: number) => {
|
|
|
|
|
ListItem() {
|
|
|
|
|
Row() {
|
|
|
|
|
Text(`通道${loopIdx+1}`).fontColor('#fff').fontSize(12).width(50)
|
|
|
|
|
Slider({
|
|
|
|
|
value: this.editingTask.loops[loopIdx].pwm || 0,
|
|
|
|
|
min: 0,
|
|
|
|
|
max: 100,
|
|
|
|
|
style: SliderStyle.OutSet
|
|
|
|
|
})
|
|
|
|
|
.layoutWeight(1)
|
|
|
|
|
.trackColor('#333')
|
|
|
|
|
.selectedColor('#22fdc8')
|
|
|
|
|
.onChange((val: number) => {
|
|
|
|
|
this.editingTask.loops[loopIdx].pwm = val;
|
|
|
|
|
})
|
|
|
|
|
Text(`${Math.round(this.editingTask.loops[loopIdx].pwm || 0)}%`)
|
|
|
|
|
.fontColor('#22fdc8').fontSize(12).width(40).textAlign(TextAlign.End)
|
|
|
|
|
}.width('100%').padding({top: 5, bottom: 5})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}.height(150)
|
|
|
|
|
} // end if
|
|
|
|
|
}.width('100%')
|
|
|
|
|
} // end Scroll
|
|
|
|
|
.layoutWeight(1)
|
|
|
|
|
.width('100%')
|
|
|
|
|
|
|
|
|
|
// Buttons
|
|
|
|
|
Row() {
|
|
|
|
|
Button('取消').onClick((): void => {this.showTaskDialog = false;}).backgroundColor('#666')
|
|
|
|
|
Button('保存').onClick((): void => {this.saveTask();}).backgroundColor('#22fdc8').fontColor('#000')
|
|
|
|
|
}.width('100%').justifyContent(FlexAlign.SpaceAround).margin({ top: 20 })
|
|
|
|
|
Button('取消').onClick((): void => {this.showTaskDialog = false;}).backgroundColor('#666').width('40%')
|
|
|
|
|
Button('保存').onClick((): void => {this.saveTask();}).backgroundColor('#22fdc8').fontColor('#000').width('40%')
|
|
|
|
|
}.width('100%').justifyContent(FlexAlign.SpaceAround).margin({ top: 10, bottom: 10 })
|
|
|
|
|
}
|
|
|
|
|
.backgroundColor('#1d3553')
|
|
|
|
|
.padding(20)
|
|
|
|
|
.width('90%')
|
|
|
|
|
.height('80%') // Taller dialog
|
|
|
|
|
.borderRadius(10)
|
|
|
|
|
}.position({x:0,y:0}).zIndex(100)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.padding({ left: 15, right: 15, top: 15, bottom: 80 })
|
|
|
|
|
|
|
|
|
|
if (this.isLoading) {
|
|
|
|
|
Stack() {
|
|
|
|
|
@ -529,7 +703,15 @@ export struct AutoMode {
|
|
|
|
|
Row() {
|
|
|
|
|
Text(title).fontColor('#22fdc8').fontWeight(FontWeight.Bold)
|
|
|
|
|
Blank()
|
|
|
|
|
Text(task.execTime).fontColor('#fff')
|
|
|
|
|
Column() {
|
|
|
|
|
Text(task.timeType === 0 ? '定时时间' : '经纬度时间')
|
|
|
|
|
.fontSize(10)
|
|
|
|
|
.fontColor('#aaa')
|
|
|
|
|
.alignSelf(ItemAlign.End)
|
|
|
|
|
Text(task.execTime)
|
|
|
|
|
.fontColor('#fff')
|
|
|
|
|
.alignSelf(ItemAlign.End)
|
|
|
|
|
}
|
|
|
|
|
}.width('100%').margin({ bottom: 5 })
|
|
|
|
|
|
|
|
|
|
Row() {
|
|
|
|
|
|