---
# System prepended metadata

title: Device Manager - Cron Job

---

# Device Manager - Cron Job

# Part I - Introduction

<!-- flowchart, architecture -->

## Brief System Architecture

### Services
- **DM-API** service
- **uService Manager** service
- **DM-CronJob** service
  - DM-CronJob **Supervisor**
  - DM-CronJob **Worker(s)**
![](https://i.imgur.com/sJkYaVt.png)

### API Protocols
- **REST** APIs
  - to **DM-API** services
- **gRPC** APIs
  - to other services
![](https://i.imgur.com/XTsfoXi.png)



# Part II - Spec

## Job Definition


### Job Subjects

- **system control**
  <!-- app installation, ... -->
- **update info**
- **power schedule**
  <!-- when to switch on/off -->
- **firmware OTA**
- **broadcast**
- **handle RS232 command**
- **scale up**

```json=
// system control - set volume
{
  "id": "1",
  "config": [
    {
      "subject": "system_control",
      "command": "volume",
      "content": 0
    },
    {
      "subject": "system_control",
      "command": "mute",
      "content": "off"
    }
  ]
}
```


### Schedule Types

- **none**
  for manual execution
- **one time**
- **recurring** 
  period: daily, weekly

```json=
// one time
{
  "id": "2",
  "time": "Fri Feb 24 2023 09:05:00 GMT+0800 (Taipei Standard Time)",
}

// recurring - weekly
{
  "id": "3",
  "weekday": [
    1,
    3,
    5
  ],
  "time": "Tue Sep 14 9999 07:30:00 GMT+0800 (Taipei Standard Time)"
}
```


## Job API

### Service Methods

<!-- methods of DM Job API -->
- **Run()**
- **Stop()**
- **Update()**
- **Get()**

<!-- code block :: protobuf -->

```protobuf=
service DmJobApi {
  // run job
  rpc Run(JobData) returns (ApiMessageResponse)

  // stop job by job ID
  rpc Stop(DmJobStopRequest) returns (ApiMessageResponse)

  // update job
  rpc Update(JobData) returns (ApiMessageResponse)

  // get job data by job ID
  rpc Get(DmJobGetRequest) returns (JobData)
}
```

### Requests and Reponses

- Job data
  - identifiers
  - scheduling
  - assignment and status
  - core data

```protobuf=
message JobData {
  // identifiers
  string id = 1;
  string entity_id = 2;

  // scheduling
  int32 schedule_type = 3;
  repeated int32 weekday = 4;
  string time = 5;
  string start_time = 6;
  string end_time = 7;

  // assignment and status
  bool running = 8;
  string worker_id = 10;
  repeated JobStatus statuses = 11;

  // core data
  string job_name = 12;
  repeated string devices = 13;
  repeated string groups = 14;
  google.protobuf.Struct action = 9;
}
```

- Other requests and responses

<!-- code block :: protobuf -->

```protobuf=
message DmJobStopRequest {
  // job ID
  string id = 1;
}

message DmJobGetRequest {
  // job ID
  string id = 1;
}

message ApiMessageResponse {
  int32 status = 1;
  int32 responseCode = 2;
  int32 messageCode = 3;
  string message = 4;
}
```

----


# Part III - Implementation

- set up jobs
- handle job events
- post-trigger (project-specific, optional)


## Set Up Jobs
- call run/stop/update/get job APIs
  - **gRPC** API
  - from **DM-API** Service
  - to **DM-CronJob Supervisor**

![fig. DM-API call run job API.](https://i.imgur.com/0a8eWTL.png)


<!-- code block: run job -->

```javascript=
const { Manager } = require('@vs-uservice/sdk');


/*
 * Convert job data to ProtoBuf
 * @param {object} jsonData
 * @returns {object}
 */
const _convertToProtoBuf = (jsonData) => {};


// connect to DM-CronJob Supervisor by app name
// use DMJobApi Service
const client = await Manager.getService({
  name: 'dm-cronjob-supervisor'
}).DmJobApi;

// prepare job data in JSON
const rawJobData = ({
  // job ID: string
  id: JOB_ID,
  // entity ID: string
  entity_id: ENTITY_ID,
  // ---- other fields ---- //
  // ...
  // action: object
  action: ACTION,
});

// convert job data to ProtoBuf
const jobData = _convertToProtoBuf(rawJobData);

// send request to run job
const result = await client.Run(jobData);
```


## Handle Job Events

![fig. DM-CronJob Workers handle job events.](https://i.imgur.com/fKrxO1t.png)


- Event listener


<!-- code block :: event listener -->

```typescript=
// drive valid device IDs 
// based on device license, online status (on/off), and so on
const _deriveValidDeviceIds = (
  deviceIds: string[],
  groupIds: string[],
): string[] => {};

// convert message
// e.g. generate URLs for broadcast and firmware OTA update
// e.g. generate RS232 commands
const _convertMessage = (message: object): object => {};

@Injectable()
class TargetEventListener {

  @OnEvent(TARGET_EVENT_NAME)
  async handleEvent(payload: TargetEventPayload) {

    // call remote control API of DM-API service
    await this.dmApiService.remoteControl({
      subject: payload.subject,
      entityId: payload.entity_id,
      deviceIds: _deriveValidDeviceIds(payload.devices, payload.groups),
      jobName: payload.job_name,
      message: _convertMessage(payload.message),
    });

    // emit action completed event
    this.complete(new ActionCompletedEvent({
      jobId: payload.job_id,
      action: TARGET_EVENT_NAME,
    }));
  }
}
```

- Event payload

```typescript=
// target event payloadpayload
class TargetEventPayload {
  job_id: string;
  job_name?: string;
  subject: string;
  // ---- other fields ---- //
  // ... 
}
```

## Post-Trigger (Project-Specific, Optional)

- call remote control API
  - **REST** API
  - from **DM-CronJob Worker**
  - to **DM-API** Service

![fig. DM-CronJob Workers call remote control API.](https://i.imgur.com/I6COHnp.png)



<!-- code block :: event listener -->

``` typescript=
const dmApiServiceKeyHeader: string;
const dmApiServiceKey; string;

await this.dmApi.remoteControl({
  headers: {
    [dmApiServiceKeyHeader]: dmApiServiceKey,
  },
  pathParams: {
    entity_id: entityId,
    subject,
  },
  data: {
    entity_id: entityId,
    devices: deviceIds,
    jobName,
    // ---- other fields ---- //
    // ...
  },
});
```

<!--
### Part IV -  (T.B.D.)

## Section 6
-->

----