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