Skip to content

Commit

Permalink
Add quick run CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
Krisjanis Veinbahs committed Feb 24, 2022
1 parent 4cfbae6 commit 7fa1e5b
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 61 deletions.
44 changes: 39 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,59 @@

DIP Testbed Platform is an academic work which allows users to remotely program and experience physical, embedded devices through various virtual interfaces (uni-directional webcam stream, bi-directional serial connection stream).

## Installation
### Quick install
## Quick installation & usage
Download the CLI tool:
```bash
curl https://github.com/kshaa/dip-testbed-dist/releases/latest/download/client_install.sh | bash
```

Create a local authentication session:
```bash
dip_client session-auth -u <username> -p <password>
```

Upload software to the platform, forward it to a hardware board, run a serial connection against it:
```bash
dip_client quick-run -f firmware.bit -b ${BOARD_UUID}
```

_Note: This assumes usage of bash, AMD64 architecture, testbed.veinbahs.lv as default server_
_Note: Also the default buttonled interface is used_
_Note: Quick run has all of the underlying mechanics configurable, see options with `quick-run --help`_

## Detailed platform usage

### Manual install
### Installation
- Download `https://github.com/kshaa/dip-testbed-dist/releases/latest/download/dip_client_${TARGET_ARCH}`
- Store in `${PATH}`
- Set executable bit
- Set static URL using `dip_client session-static-server -s http://testbed.veinbahs.lv`
- Set control URL using `dip_client session-control-server -s ws://testbed.veinbahs.lv`

## Usage
Configure academig DIP Testbed platform server:
```bash
dip_client session-static-server -s http://testbed.veinbahs.lv
dip_client session-control-server -s ws://testbed.veinbahs.lv
```

Authenticate:
```bash
dip_client session-auth -u <username> -p <password>
```

Upload software to platform:
```bash
dip_client software-upload -f firmware.bit
```

Forward software to a hardware board:
```bash
dip_client hardware-software-upload --hardware-id ${BOARD_UUID} --software-id ${SOFTWARE_UUID}
```

Create a serial connection to the board:
```
dip_client hardware-serial-monitor --hardware-id ${BOARD_UUID} -t buttonleds
```

## Documentation
- See 🌼 🌻 [docs](./docs/README.md) 🌻 🌼 for user-centric documentation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ class SoftwareController(
maybeUser
.leftMap(databaseErrorResult)
.flatMap(_.toRight(authorizationErrorResult))
.flatMap(user =>
.flatMap(user => {
println(request.body.file("software"), request.body.dataParts.get("name"))
(request.body.file("software"), request.body.dataParts.get("name").flatMap(_.headOption)).tupled
.toRight(Failure("Request must contain 'software' file and 'name' field").withHttpStatus(BAD_REQUEST))
.flatMap {
Expand All @@ -50,8 +51,8 @@ class SoftwareController(
Failure("Request too large").withHttpStatus(BAD_REQUEST),
)
.map(_ => (data, name, user))
},
),
}
})
)

val authWithSoftwareBytes = authWithSoftwareData.flatMap {
Expand Down
6 changes: 6 additions & 0 deletions backend/web/conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ play = {
http.secret.key = "31imdCGyKb5IwITHhzlo"
application.loader = diptestbed.web.DIPTestbedLoader
akka.actor-system = "DIPTestbed"
# CSRF checks required only if cookies are present
# A lone Authorization header can bypass CSRF checks
filters.csrf.header.protectHeaders = {
"Cookie" = "*"
"Authorization" = "nocheck"
}
}

# Akka
Expand Down
1 change: 1 addition & 0 deletions client/potato.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1
7 changes: 5 additions & 2 deletions client/src/engine/engine_serial_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from src.domain.monitor_message import SerialMonitorMessageToAgent, SerialMonitorMessageToClient, MonitorUnavailable
from src.engine.engine_events import COMMON_ENGINE_EVENT, StartSerialMonitor, SerialMonitorStartSuccess, \
SerialMonitorStartFailure, ReceivedSerialBytes, SendingBoardBytes, StoppingSerialMonitor, StoppedSerialMonitor, \
SerialMonitorAboutToStart, SerialMonitorAlreadyConfigured, MonitorDied, LifecycleEnded
SerialMonitorAboutToStart, SerialMonitorAlreadyConfigured, MonitorDied, LifecycleEnded, UploadingBoardSoftware
from src.engine.engine_state import EngineState, ManagedQueue, EngineBase
from src.service.managed_serial import ManagedSerial
from src.service.managed_serial_config import ManagedSerialConfig
Expand Down Expand Up @@ -178,4 +178,7 @@ async def effect_project(self, previous_state: EngineSerialMonitorState, event:
message = f"Agent engine stopped"
if event.reason is not None:
message = f"{message}, reason: {event.reason.text()}"
await previous_state.base.outgoing_message_queue.put(MonitorUnavailable(message))
await previous_state.base.outgoing_message_queue.put(MonitorUnavailable(message))
elif isinstance(event, UploadingBoardSoftware):
await previous_state.base.outgoing_message_queue.put(MonitorUnavailable(
"Serial connection broken by new board software upload"))
15 changes: 10 additions & 5 deletions client/src/service/backend.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Module for backend management service definitions"""

import os
from typing import List, TypeVar, Dict, Optional
from dataclasses import dataclass
import base64
Expand Down Expand Up @@ -87,8 +87,8 @@ def software_list(self) -> Result[List[Software], BackendManagementError]:

def software_upload(
self,
software_name: str,
file_path: str
file_path: str,
software_name: str
) -> Result[Software, BackendManagementError]:
pass

Expand Down Expand Up @@ -174,6 +174,7 @@ def static_get_json_result(
if headers is None:
headers = {}
try:
LOGGER.debug(f"HTTP GET JSON: {url_text_result.value}, headers: {headers}")
response = requests.get(url_text_result.value, headers=headers)
LOGGER.debug(ManagedURL.response_log_text(response))
return BackendService.response_to_result(response, content_decoder)
Expand Down Expand Up @@ -201,9 +202,11 @@ def static_post_json_result(
if files is None: headers = {}
try:
if payload is None:
LOGGER.debug(f"HTTP POST JSON: {url_text_result.value}, headers: {headers}, files:{ files }")
response = requests.post(url_text_result.value, headers=headers, files=files)
else:
encoded_payload = payload_encoder.encode(payload) if payload_encoder is not None else payload
LOGGER.debug(f"HTTP POST JSON: {url_text_result.value}, payload: {encoded_payload}, headers: {headers}, files:{files}")
response = requests.post(url_text_result.value, encoded_payload, headers=headers, files=files)
# Parse response
LOGGER.debug(ManagedURL.response_log_text(response))
Expand Down Expand Up @@ -275,13 +278,15 @@ def software_list(self) -> Result[List[Software], str]:

def software_upload(
self,
software_name: str,
file_path: ExistingFilePath
file_path: ExistingFilePath,
software_name: Optional[str]
) -> Result[Software, BackendManagementError]:
"""Upload a new software"""
path = f"{self.config.api_prefix}/software"
decoder = s11n_json.SOFTWARE_DECODER_JSON
files = {'software': open(file_path.value, 'rb')}
if software_name is None:
software_name = os.path.basename(file_path.value)
payload = {'name': software_name}
if self.config.auth is None: return BackendService.auth_error
return self.static_post_json_result(path, decoder, payload, None, self.config.auth.auth_headers(), files)
Expand Down
51 changes: 47 additions & 4 deletions client/src/service/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,10 +216,25 @@ def hardware_serial_monitor(
control_server_str: Optional[str],
hardware_id_str: str,
monitor_type_str: str,
monitor_script_path_str: str
monitor_script_path_str: Optional[str]
):
pass

@staticmethod
async def quick_run(
config_path_str: Optional[str],
control_server_str: Optional[str],
static_server_str: Optional[str],
username_str: Optional[str],
password_str: Optional[str],
file_path: Optional[str],
software_name: Optional[str],
hardware_id_str: str,
monitor_type_str: str,
monitor_script_path_str: Optional[str]
) -> Result[DIPRunnable, DIPClientError]:
pass

@staticmethod
def execute_runnable_result(agent_result: Result[DIPRunnable, DIPClientError]):
pass
Expand Down Expand Up @@ -633,14 +648,14 @@ def software_upload(
static_server_str: Optional[str],
username: Optional[str],
password: Optional[str],
software_name: str,
software_name: Optional[str],
file_path: str,
) -> Result[Software, DIPClientError]:
backend_result = CLI.parsed_backend(config_path_str, None, static_server_str, username, password)
if isinstance(backend_result, Err): return Err(backend_result.value)
file_result = ExistingFilePath.build(file_path)
if isinstance(file_result, Err): return Err(file_result.value.of_type("software"))
return backend_result.value.software_upload(software_name, file_result.value)
return backend_result.value.software_upload(file_result.value, software_name)

@staticmethod
def software_download(
Expand Down Expand Up @@ -687,7 +702,7 @@ def hardware_serial_monitor(
monitor_script_path_str: Optional[str]
) -> Result[MonitorSerial, DIPClientError]:
# Build backend
backend_result = CLI.parsed_backend(control_server_str, None, None, None)
backend_result = CLI.parsed_backend(control_server_str, None, None, None, None)
if isinstance(backend_result, Err): return Err(backend_result.value)

# Hardware id
Expand All @@ -709,6 +724,34 @@ def hardware_serial_monitor(
websocket = WebSocket(url_result.value, decoder, encoder)
return monitor_serial.resolve(websocket, monitor_script_path_str)

@staticmethod
async def quick_run(
config_path_str: Optional[str],
control_server_str: Optional[str],
static_server_str: Optional[str],
username_str: Optional[str],
password_str: Optional[str],
file_path: Optional[str],
software_name: Optional[str],
hardware_id_str: str,
monitor_type_str: str,
monitor_script_path_str: Optional[str]
) -> Result[DIPRunnable, DIPClientError]:
# Upload software to platform
upload_result = await CLI.software_upload(
config_path_str, static_server_str, username_str, password_str, software_name, file_path)
if isinstance(upload_result, Err): return Err(upload_result.value)
software: Software = upload_result.value
# Forward software to board
forward_error = CLI.hardware_software_upload(
config_path_str, static_server_str, hardware_id_str, str(software.id))
if forward_error is not None: return Err(forward_error)
# Create serial monitor connection to board
monitor_result = CLI.hardware_serial_monitor(
config_path_str, control_server_str, hardware_id_str, monitor_type_str, monitor_script_path_str)
if isinstance(monitor_result, Err): return Err(monitor_result.value)
return Ok(monitor_result.value)

@staticmethod
async def execute_runnable_result(
runnable_result: Result[DIPRunnable, DIPClientError],
Expand Down
Loading

0 comments on commit 7fa1e5b

Please sign in to comment.