# 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)**

### API Protocols
- **REST** APIs
- to **DM-API** services
- **gRPC** APIs
- to other services

# 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**

<!-- 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

- 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

<!-- 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
-->
----