Skip to content

Commit

Permalink
Feature/poweroff component (#172)
Browse files Browse the repository at this point in the history
## Feature
- Simple component that provides a poweroff and reboot interface
- Simple backend implementation - no scheduling
- Connecting the component calls to the websocket e2e
## Fixes
- Try to reconnect if websocket closes

Resolves #172
  • Loading branch information
vaslabs authored Apr 10, 2021
1 parent 388c864 commit c4e6129
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 22 deletions.
35 changes: 32 additions & 3 deletions service/web/pkg/action_dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,27 @@ type Action interface {
type Display_Live_Info struct {
}

type Power_Off struct {
}

type Reboot struct {
}

const DISPLAY_LIVE_INFO = "DISPLAY_LIVE_INFO"
const REBOOT = "REBOOT"
const POWER_OFF = "POWER_OFF"

func (c Display_Live_Info) Action_Type() string {
func (c *Display_Live_Info) Action_Type() string {
return DISPLAY_LIVE_INFO
}

func (r *Reboot) Action_Type() string {
return REBOOT
}
func (p *Power_Off) Action_Type() string {
return POWER_OFF
}

type UnrecognisedAction struct {
action_type string
}
Expand Down Expand Up @@ -57,6 +72,14 @@ func (display_live_info *Display_Live_Info) execute(session *net.Session) {
session.Send(message)
}

func (reboot *Reboot) execute(session *net.Session) {
session.Send(System_Reboot())
}

func (power_off *Power_Off) execute(session *net.Session) {
session.Send(System_Power_Off())
}

func Parse_Action(r *io.Reader) (Action, error) {
json_reader := viper.New()
json_reader.SetConfigType("json")
Expand All @@ -66,11 +89,17 @@ func Parse_Action(r *io.Reader) (Action, error) {
return nil, &UnparseableAction{err}
}
action_type := json_reader.GetString("Action_Type")
if action_type == DISPLAY_LIVE_INFO {
switch action_choice := action_type; action_choice {
case DISPLAY_LIVE_INFO:
return &Display_Live_Info{}, nil
} else {
case REBOOT:
return &Reboot{}, nil
case POWER_OFF:
return &Power_Off{}, nil
default:
return nil, &UnrecognisedAction{action_type}
}

}

func CreateDispatcher() net.Dispatcher {
Expand Down
22 changes: 17 additions & 5 deletions service/web/pkg/power_off.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,30 @@ import (
)

type power_off_response struct {
message string
exit_code int32
Message string
Exit_Code int32
}

func power_off() power_off_response {
func System_Power_Off() power_off_response {
message, err := shell.RunSingle("poweroff")
exit_code := 0
if err == nil {
exit_code = 1
}
return power_off_response{
message: message,
exit_code: int32(exit_code),
Message: message,
Exit_Code: int32(exit_code),
}
}

func System_Reboot() power_off_response {
message, err := shell.RunSingle("reboot")
exit_code := 0
if err == nil {
exit_code = 1
}
return power_off_response{
Message: message,
Exit_Code: int32(exit_code),
}
}
1 change: 1 addition & 0 deletions ui/pi-web-agent-app/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<div>
<app-live-info></app-live-info>
<app-poweroff></app-poweroff>
</div>

<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
Expand Down
10 changes: 7 additions & 3 deletions ui/pi-web-agent-app/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,24 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {MatDividerModule} from '@angular/material/divider';
import {MatListModule} from '@angular/material/list';
import { HttpClientModule } from '@angular/common/http';

import { PoweroffComponent } from './poweroff/poweroff.component';
import {MatButtonModule} from '@angular/material/button';
import {MatIconModule} from '@angular/material/icon';

@NgModule({
declarations: [
AppComponent,
LiveInfoComponent
LiveInfoComponent,
PoweroffComponent
],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
MatDividerModule,
MatListModule,
HttpClientModule
HttpClientModule,
MatIconModule
],
providers: [],
bootstrap: [AppComponent]
Expand Down
6 changes: 2 additions & 4 deletions ui/pi-web-agent-app/src/app/live-info/live-info.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { interval } from 'rxjs';
import { PiControlService } from '../pi-control.service';
import { SystemInfo, SystemInfoService } from '../system-info.service';

Expand All @@ -23,7 +24,6 @@ export class LiveInfoComponent implements OnInit {
this.periodicUpdate(this.systemInfoService);
this.piControl.eventSource()?.subscribe(
(next: any) => {
console.log('Received ' + JSON.stringify(next));
if (next.OS_Info) {
this.systemInfo.OS_Info = next.OS_Info;
this.systemInfo.Kernel = next.Kernel;
Expand All @@ -34,9 +34,7 @@ export class LiveInfoComponent implements OnInit {
}

private periodicUpdate(infoService: SystemInfoService): void {
console.log('Sending command for display live info');
infoService.fetchSystemInfo();
setTimeout(() => this.periodicUpdate(infoService), 1000);
interval(1000).subscribe(() => infoService.fetchSystemInfo());
}

}
15 changes: 13 additions & 2 deletions ui/pi-web-agent-app/src/app/pi-control.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { Observable, BehaviorSubject, Observer } from 'rxjs';
import { WebsocketService } from './websocket.service';

@Injectable({
providedIn: 'root'
})
Expand All @@ -17,7 +16,15 @@ export class PiControlService {
private zone: NgZone,
private websocketService: WebsocketService
) {
this.websocketService.connect((next: any) => this.messageSource.next(next));
const broker = (next: any) => this.messageSource.next(next);
this.connectToSocket(broker);
}

private connectToSocket(broker: (next: any) => void): void {
this.websocketService.connect(broker, (error: any) => {
console.log(`Attempting to reconnect to socket after ${JSON.stringify(error)}`);
this.connectToSocket(broker);
});
}

commandSink(): Subject<any> | null {
Expand All @@ -31,6 +38,10 @@ export class PiControlService {
sendCommand(command: PiCommand): void {
this.commandSink()?.next(command);
}

private ping(): void {
this.commandSink()?.next('ping');
}
}

export interface PiCommand {
Expand Down
8 changes: 8 additions & 0 deletions ui/pi-web-agent-app/src/app/poweroff/poweroff.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<mat-action-list>
<button mat-list-item mat-icon-button color="warn" aria-label="Poweroff system" (click)="poweroff()">
<mat-icon>power_settings_new</mat-icon> Poweroff
</button>
<button mat-list-item mat-icon-button color="warn" aria-label="Reboot system" (click)="reboot()">
<mat-icon>cached</mat-icon> Reboot
</button>
</mat-action-list>
Empty file.
25 changes: 25 additions & 0 deletions ui/pi-web-agent-app/src/app/poweroff/poweroff.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { PoweroffComponent } from './poweroff.component';

describe('PoweroffComponent', () => {
let component: PoweroffComponent;
let fixture: ComponentFixture<PoweroffComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PoweroffComponent ]
})
.compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(PoweroffComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
24 changes: 24 additions & 0 deletions ui/pi-web-agent-app/src/app/poweroff/poweroff.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Component, OnInit } from '@angular/core';
import { PiControlService } from '../pi-control.service';

@Component({
selector: 'app-poweroff',
templateUrl: './poweroff.component.html',
styleUrls: ['./poweroff.component.scss']
})
export class PoweroffComponent implements OnInit {

constructor(private piControlService: PiControlService) { }

ngOnInit(): void {
}

poweroff(): void {
this.piControlService.sendCommand({Action_Type: 'POWER_OFF'});
}

reboot(): void {
this.piControlService.sendCommand({Action_Type: 'REBOOT'});
}

}
10 changes: 5 additions & 5 deletions ui/pi-web-agent-app/src/app/websocket.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ export class WebsocketService {
const host = window.location.host;
return `${protocol}//${host}/api/control/stream`;
}
public connect(subscriber: (next: any) => void): void {
public connect(subscriber: (next: any) => void, erroWatcher: (error: any) => void): void {
try {
if (!this.subject) {
if (!this.subject || this.subject?.isStopped) {
console.log(`Connecting to ${this.url()} for the first time`);
this.subject = this.create();
this.subject.subscribe(subscriber);
console.log('Successfully connected: ');
}
this.subject = this.create();
this.subject.subscribe(subscriber, erroWatcher);
console.log('Successfully connected: ');
} catch (error) {
console.log(`Failed to connect due to ${error}`);
}
Expand Down

0 comments on commit c4e6129

Please sign in to comment.