# Electrician Timing SDK

`@ray-js/electrician-timing-sdk` provides a unified timing solution for electrician devices, including:

- Electrician timers (cycle / random / inching)
- Countdown
- Cloud timers
- Conflict detection and conflict resolution
- Online status checks

## Installation

```bash
npm i @ray-js/electrician-timing-sdk
```

## API Overview

### Lifecycle

| API | Description |
| --- | --- |
| `init(options)` | Initialize SDK and load runtime state |
| `changeConfig(options)` | Update config; re-init when target device/group changes |
| `destroy()` | Clear internal state, listeners, and config |

### Cloud Timer

| API | Description |
| --- | --- |
| `addCloudTimer(data, option?)` | Add one cloud timer |
| `batchAddCloudTimer(dataList, option?)` | Add cloud timers in batch |
| `updateCloudTimer(data, option?)` | Update cloud timer content |
| `updateCloudTimerStatus(data, option?)` | Enable/disable cloud timer |
| `removeCloudTimer(id)` | Remove cloud timer |
| `onCloudUpdate(cb)` | Subscribe cloud timer update event |
| `offCloudUpdate(cb)` | Unsubscribe cloud timer update event |

### Countdown

| API | Description |
| --- | --- |
| `createCountdown(code, countdown, option?)` | Create countdown |
| `cancelCountdown(code)` | Cancel countdown |

### Custom DP Timer

| API | Description |
| --- | --- |
| `addDpTimer(code, data, option?)` | Add custom DP timer |
| `updateDpTimer(code, data, option?)` | Update custom DP timer |
| `updateDpTimerStatus(code, id, status, option?)` | Enable/disable custom DP timer |
| `removeDpTimer(code, id)` | Remove custom DP timer |

### Electrician Shortcuts (`electri`)

| API | Description |
| --- | --- |
| `electri.cycle.add/update/updateStatus/remove` | CRUD + status for cycle timer |
| `electri.cycle.getConfig/validateMax/validateRepeat` | Cycle timer config and validation |
| `electri.random.add/update/updateStatus/remove` | CRUD + status for random timer |
| `electri.random.getConfig/validateMax/validateRepeat` | Random timer config and validation |
| `electri.inching.add/update/updateStatus/remove` | CRUD + status for inching timer |
| `electri.inching.getConfig/validateMax/validateRepeat` | Inching timer config and validation |

### Online Status

| API | Description |
| --- | --- |
| `isLANOnline()` | Check LAN-online status |
| `isLocalOnline()` | Check local-online status |

## Initialization

### Basic rules

- You must provide at least one of `devId` or `groupId`.
- `devId` takes priority when both are provided.
- `type` supports `ele` and `custom`.

### Common options of `init(options)`

| Field | Required | Description |
| --- | --- | --- |
| `devId` | No | Device ID |
| `groupId` | No | Group ID |
| `type` | Yes | `ele` or `custom` |
| `category` | No | Cloud timer category, default `sdk_schedule`，**Modifications to new projects are not recommended**|
| `conflictModallId` | No | Default conflict modal id |
| `is24Hour` | No | 24-hour display |
| `combineSameData` | No | Merge same timer data across channels, default `true` |

### Extra options for `type: 'ele'`

| Field | Required | Description |
| --- | --- | --- |
| `switches` | No | Switch codes; auto-detected when omitted |
| `countdowns` | No | Countdown config list or countdown code list |
| `dpTimerConfig` | No | Custom DP timer config map |
| `supportCycle/supportRandom/supportInching/supportCloud` | No | Capability flag: `auto` / `y` / `n` |
| `cycleCode/randomCode/inchingCode` | No | Explicit timer dp code |

### Extra options for `type: 'custom'`

| Field | Required | Description |
| --- | --- | --- |
| `switches` | Yes | Switch code list |
| `countdowns` | Yes | Countdown config list |
| `dpTimerConfig` | Yes | Custom timer dp config map |
| `supportCloud` | No | Cloud timer capability |

## Conflict modal (`ConflictPopup`)

When timer APIs are called with `{ useDefaultModal: true }`, the SDK resolves conflicts by taking the **top page** in the stack, calling `selectComponent('#<id>')` on the mounted conflict component, and invoking **`show(conflictData, validateData)`** to render the conflict list. After the user confirms, the component runs the same resolution path as the main package (DP publish / cloud timers, etc.). Therefore **`conflictModallId` passed to `init` (default `smart-conflict-popup`) must exactly match the popup node `id` in your page**; otherwise the SDK throws because the component cannot be found.

```mermaid
flowchart TB
  A[Timer API + useDefaultModal: true] --> B[SDK validates conflicts]
  B -->|No conflict| C[Continue / pass]
  B -->|Conflict| D["selectComponent('#conflictModallId').show(...)"]
  D --> E[ConflictPopup renders list]
  E --> F{User action}
  F -->|Confirm| G[Internal resolve applies changes]
  F -->|Cancel/close| H[API returns cancel]
  G --> I[success, or toast + reject on failure]
```

### Using `ConflictPopup` in React

```tsx
import { ConflictPopup } from '@ray-js/electrician-timing-sdk/lib/components';

// Mount near the page root; id must match init({ conflictModallId }) (default can be omitted)
<ConflictPopup
  id="smart-conflict-popup"
  title="Timer conflict"
  description="Replace conflicting timers?"
  locale={locale}
  cancelText="Cancel"
  okText="Confirm"
/>
```

List content is injected when the SDK calls `show`; you normally only set title, description, `locale`, and button labels. Follow your Ray + React build setup for component registration.

### `ConflictPopup` props

| Prop | Required | Description |
| --- | --- | --- |
| `title` | Yes | Modal title |
| `locale` | Yes | `Locale` object: weekdays, type labels, `conflictFailure`, DP strings, etc. |
| `id` | No | Must match `init`’s `conflictModallId` for `selectComponent` |
| `description` | No | Subtitle / helper text |
| `cancelText` / `okText` | No | Button labels (defaults exist) |
| `themeColor` | No | Icon background color |
| `hideMask` | No | Hide overlay mask |
| `visble` | No | Visibility flag (same spelling as source; `show` usually drives display) |
| `usesolt` | No | Use slot for custom list area (same spelling as source) |
| `data` / `validateData` | No | Normally passed by SDK in `show`, not hard-coded in template |

## Complete API Examples

### 1) Initialize

```mermaid
flowchart LR
  A[Pass devId/groupId + type] --> B[init loads runtime state]
  B --> C[Cloud/electri/countdown capabilities]
  C --> D[Ready for timer APIs]
```

```ts
import { init } from '@ray-js/electrician-timing-sdk';

await init({
  devId: 'your-device-id',
  type: 'ele',
  supportCloud: 'auto',
});
```

### 2) Cycle timer

```mermaid
flowchart LR
  A[Build cycle timer data] --> B[electri.cycle.add]
  B --> C{Conflict check}
  C -->|Pass| D[Publish DP / success]
  C -->|Conflict| E[Return conflict + validateData]
  C -->|Cancel| F[cancel]
```

```ts
import { electri } from '@ray-js/electrician-timing-sdk';

await electri.cycle.add(
  {
    id: -1,
    type: 'sdk_cycle',
    startTime: 8 * 60,
    endTime: 18 * 60,
    week: [1, 1, 1, 1, 1, 0, 0],
    status: true,
    actions: [{ code: 'switch_1' }],
    onHoldTime: 100,
    offHoldTime: 100,
  },
  { useDefaultModal: true }
);
```

### 3) Random timer

```mermaid
flowchart LR
  A[Build random timer data] --> B[electri.random.add]
  B --> C{Conflict check}
  C -->|Pass| D[Publish DP / success]
  C -->|Conflict| E[Return conflict + validateData]
  C -->|Cancel| F[cancel]
```

```ts
import { electri } from '@ray-js/electrician-timing-sdk';

await electri.random.add(
  {
    id: -1,
    type: 'sdk_random',
    startTime: 10 * 60,
    endTime: 12 * 60,
    week: [0, 0, 1, 0, 0, 0, 0],
    status: true,
    actions: [{ code: 'switch_1' }],
  },
  { useDefaultModal: true }
);
```

### 4) Inching timer

```mermaid
flowchart LR
  A[Build inching data time + actions] --> B[electri.inching.add]
  B --> C{Conflict check}
  C -->|Pass| D[Publish DP / success]
  C -->|Conflict| E[Return conflict + validateData]
  C -->|Cancel| F[cancel]
```

```ts
import { electri } from '@ray-js/electrician-timing-sdk';

await electri.inching.add(
  {
    id: -1,
    type: 'sdk_inching',
    time: 300,
    status: true,
    actions: [{ code: 'switch_1' }],
  },
  { useDefaultModal: true }
);
```

### 5) Cloud timer (single + batch)

```mermaid
flowchart TB
  subgraph single["Single add"]
    S1[addCloudTimer single data] --> S2{Conflict check}
    S2 --> S3[success / cancel / conflict]
  end
  subgraph batch["Batch add"]
    B1[batchAddCloudTimer data list] --> B2{Conflict check}
    B2 --> B3[success / cancel / conflict]
  end
```

```ts
import { addCloudTimer, batchAddCloudTimer } from '@ray-js/electrician-timing-sdk';

await addCloudTimer(
  {
    aliasName: 'morning',
    startTime: 9 * 60,
    week: [1, 1, 1, 1, 1, 1, 1],
    status: true,
    actions: [{ code: 'switch_1', value: true }],
    isAppPush: false,
  },
  { useDefaultModal: true }
);

await batchAddCloudTimer(
  [
    {
      aliasName: 'workday',
      startTime: 8 * 60,
      week: [1, 1, 1, 1, 1, 0, 0],
      status: true,
      actions: [{ code: 'switch_1', value: true }],
      isAppPush: false,
    },
  ],
  { useDefaultModal: true }
);
```

### 6) Countdown

```mermaid
flowchart LR
  A[createCountdown code + duration] --> B{Conflict check}
  B -->|Pass| C[Publish countdown DP]
  B -->|Conflict/Cancel| D[Return result]
  C --> E[Optional: cancelCountdown]
```

```ts
import { createCountdown, cancelCountdown } from '@ray-js/electrician-timing-sdk';

await createCountdown('countdown_1', 1800, { useDefaultModal: true });
await cancelCountdown('countdown_1');
```

### 7) Custom DP timer

```mermaid
flowchart TB
  A[addDpTimer code + data] --> B{Conflict check}
  B --> C[success / conflict]
  C --> D[updateDpTimerStatus enable/disable]
  C --> E[removeDpTimer delete]
  D --> F[Maintain lifecycle]
  E --> F
```

```ts
import { addDpTimer, updateDpTimerStatus, removeDpTimer } from '@ray-js/electrician-timing-sdk';

await addDpTimer(
  'cycle_timing',
  {
    id: 1,
    type: 'sdk_cycle',
    startTime: 360,
    endTime: 480,
    week: [1, 1, 1, 1, 1, 1, 1],
    status: true,
    actions: [{ code: 'switch_1' }],
    onHoldTime: 60,
    offHoldTime: 60,
  },
  { useDefaultModal: true }
);

await updateDpTimerStatus('cycle_timing', 1, false);
await removeDpTimer('cycle_timing', 1);
```

## Recommended Timing Workflow

Use this order for stable behavior:

1. **Initialize** with `init(options)` before any timer API call.
2. **Check capability** (cloud/electri/countdown) based on product needs.
3. **Create timer** via one of: `electri.*`, `addCloudTimer`, `createCountdown`, `addDpTimer`.
4. **Handle conflict result**:
   - `success`: done.
   - `cancel`: user canceled conflict handling.
   - `{ conflict, validateData }`: continue with your conflict UI/process.
5. **Maintain timer lifecycle** with `update*` and `update*Status`.
6. **Listen for updates** using `onConflictChange` and `onCloudUpdate`.
7. **Cleanup** with `off*` listeners and `destroy()` when leaving page/scene.

## Conflict-related return values

- `success`: operation completed
- `cancel`: user canceled conflict handling
- `pass`: internal "validated, continue" state
- `{ conflict, validateData }`: conflict detail payload

## Error Codes

| errCode | errMsg |
| --- | --- |
| 1001 | Supports a maximum of timers |
| 1002 | Timer dp config not found |
| 1003 | Timer id not found |
| 1004 | Cloud timer not supported |
| 1005 | Countdown cancel value is missing |
| 1006 | Countdown config not found |
| 1007 | DP schema not found |
| 1008 | Invalid value |
| 1009 | Cycle start time and end time cannot be equal |
| 1010 | Loop unit duration cannot exceed total duration |
| 1011 | Random timer duration must be at least 30 minutes |

## Notes

- Cloud timer is not supported in group mode by default.
- Conflict validation happens on enable-path operations (create/update-enable/enable status).
- For enum-based countdown total values, configure `totalRange` and `cancelValue` correctly.
- `combineSameData` changes how same-content timers across channels are merged.