1
0
mirror of https://github.com/skot/ESP-Miner.git synced 2025-03-17 21:32:52 +01:00

swarm init

This commit is contained in:
Ben 2023-11-25 00:19:15 -05:00
parent 7abda9914c
commit dd3a1dec87
16 changed files with 290 additions and 30 deletions

@ -1,6 +1,6 @@
{
"idf.flashType": "UART",
"idf.portWin": "COM30",
"idf.portWin": "COM36",
"idf.adapterTargetName": "esp32s3",
"idf.openOcdConfigs": [
"interface/ftdi/esp32_devkitj_v1.cfg",

@ -3,6 +3,7 @@ import { RouterModule, Routes } from '@angular/router';
import { EditComponent } from './components/edit/edit.component';
import { HomeComponent } from './components/home/home.component';
import { SwarmComponent } from './components/swarm/swarm.component';
const routes: Routes = [
{
@ -12,6 +13,10 @@ const routes: Routes = [
{
path: 'edit',
component: EditComponent
},
{
path: 'swarm',
component: SwarmComponent
}
];

@ -1,5 +1,23 @@
<app-loading></app-loading>
<app-header></app-header>
<div class="content">
<router-outlet></router-outlet>
<div class="body">
<!-- <button [routerLink]="['edit']" class="btn btn-primary edit">Settings</button> -->
<button (click)="restart()" class="btn btn-danger restart">Restart</button>
<div class="tab-container">
<div class="tab" [routerLink]="['/']" routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}">Home
</div>
<div class="tab" [routerLink]="['edit']" routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}">
Settings
</div>
<div class="tab" [routerLink]="['swarm']" routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}">
Swarm
</div>
</div>
<div class="content">
<router-outlet></router-outlet>
</div>
</div>

@ -1,14 +1,69 @@
.content {
margin-top: 150px;
.body {
margin-top: 100px;
padding-left: 10vw;
padding-right: 10vw;
padding-left: 5vw;
padding-right: 5vw;
}
@media only screen and (max-width:900px) {
.content {
.body {
padding-left: 25px;
padding-right: 25px;
}
}
.tab-container {
display: flex;
margin-top: 32px;
}
.tab {
flex-grow: 1;
flex-basis: 0;
flex-shrink: 0;
flex-direction: column;
cursor: pointer;
border-top: 1px solid #304562;
border-left: 1px solid #304562;
border-right: 1px solid #304562;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
padding: 10px;
text-align: center;
font-size: 25px;
&:not(.active) {
border-bottom: 1px solid #304562;
background-color: #1f2d40;
}
}
.content {
padding-top: 26px;
padding-left: 5vw;
padding-right: 5vw;
border-left: 1px solid #304562;
border-bottom: 1px solid #304562;
border-right: 1px solid #304562;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
padding-bottom: 26px;
}
@media only screen and (max-width:900px) {
.content {
padding-left: 10px;
padding-right: 10px;
}
}

@ -1,4 +1,7 @@
import { Component } from '@angular/core';
import { ToastrService } from 'ngx-toastr';
import { SystemService } from './services/system.service';
@Component({
selector: 'app-root',
@ -6,10 +9,20 @@ import { Component } from '@angular/core';
styleUrls: ['./app.component.scss']
})
export class AppComponent {
constructor() {
constructor(
private systemService: SystemService,
private toastr: ToastrService,
) {
}
public restart() {
this.systemService.restart().subscribe(res => {
});
this.toastr.success('Success!', 'Bitaxe restarted');
}
}

@ -14,6 +14,7 @@ import { HomeComponent } from './components/home/home.component';
import { LoadingComponent } from './components/loading/loading.component';
import { ANSIPipe } from './pipes/ansi.pipe';
import { DateAgoPipe } from './pipes/date-ago.pipe';
import { SwarmComponent } from './components/swarm/swarm.component';
const components = [
AppComponent,
@ -27,7 +28,8 @@ const components = [
HomeComponent,
LoadingComponent,
ANSIPipe,
DateAgoPipe
DateAgoPipe,
SwarmComponent
],
imports: [
BrowserModule,

@ -1,6 +1,3 @@
<div class="button-row">
<button (click)="restart()" class="btn btn-danger restart">Restart</button>
</div>
<div class="card">
<h2>Settings</h2>
<ng-container *ngIf="form != null">

@ -2,10 +2,7 @@
<h4>Loading...</h4>
</ng-template>
<ng-container>
<div class="button-row">
<button [routerLink]="['edit']" class="btn btn-primary edit">Settings</button>
<button (click)="restart()" class="btn btn-danger restart">Restart</button>
</div>
<div class="container">
<div>
@ -128,8 +125,11 @@
</div>
<div class="card mt-2">
<h2>Realtime Logs</h2>
<div id="logs" #scrollContainer>
<h2>Realtime Logs <button (click)="showLogs = !showLogs" style="margin-left: 15px;"
class="btn btn-secondary">{{showLogs ? 'Hide': 'Show'}}
Logs</button></h2>
<div *ngIf="showLogs" id="logs" #scrollContainer>
<div *ngFor="let log of logs">₿ {{log | ANSI}}</div>
</div>
</div>

@ -1,7 +1,5 @@
import { AfterViewChecked, Component, ElementRef, OnDestroy, ViewChild } from '@angular/core';
import { ToastrService } from 'ngx-toastr';
import { interval, map, Observable, shareReplay, startWith, Subscription, switchMap } from 'rxjs';
import { LoadingService } from 'src/app/services/loading.service';
import { SystemService } from 'src/app/services/system.service';
import { WebsocketService } from 'src/app/services/web-socket.service';
import { ISystemInfo } from 'src/models/ISystemInfo';
@ -22,11 +20,11 @@ export class HomeComponent implements AfterViewChecked, OnDestroy {
private websocketSubscription: Subscription;
public showLogs = false;
constructor(
private systemService: SystemService,
private toastr: ToastrService,
private loadingService: LoadingService,
private websocketService: WebsocketService
) {
@ -69,11 +67,6 @@ export class HomeComponent implements AfterViewChecked, OnDestroy {
}
}
public restart() {
this.systemService.restart().subscribe(res => {
});
this.toastr.success('Success!', 'Bitaxe restarted');
}
}

@ -0,0 +1,14 @@
<form [formGroup]="form">
<label>AxeOS Device IP: </label>
<input formControlName="ip" type="text">
<button style="margin-left: 15px;" class="btn btn-primary" (click)="add()">Add</button>
</form>
<div class="card">
<ng-container *ngIf="swarm$ | async as swarm">
<div *ngFor="let axeOs$ of swarm">
<div class="row" *ngIf="axeOs$ | async as axe">
{{axe.ip}}: {{axe.hashRate | number: '1.2-2'}}
</div>
</div>
</ng-container>
</div>

@ -0,0 +1,9 @@
form {
margin-bottom: 20px;
}
.row {
height: 20px;
width: 100%;
border: 1px solid white
}

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SwarmComponent } from './swarm.component';
describe('SwarmComponent', () => {
let component: SwarmComponent;
let fixture: ComponentFixture<SwarmComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [SwarmComponent]
});
fixture = TestBed.createComponent(SwarmComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,72 @@
import { Component } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { catchError, map, Observable, of, startWith, switchMap } from 'rxjs';
import { SystemService } from 'src/app/services/system.service';
@Component({
selector: 'app-swarm',
templateUrl: './swarm.component.html',
styleUrls: ['./swarm.component.scss']
})
export class SwarmComponent {
public form: FormGroup;
public swarm$: Observable<Observable<any>[]>;
constructor(
private fb: FormBuilder,
private systemService: SystemService,
private toastr: ToastrService
) {
this.form = this.fb.group({
ip: []
});
this.swarm$ = this.systemService.getSwarmInfo().pipe(
map(swarmInfo => {
return swarmInfo.map(({ ip }) => {
// Make individual API calls for each IP
return this.systemService.getInfo(`http://${ip}`).pipe(
startWith({ ip }),
map(info => {
return {
ip,
...info
};
}),
catchError(error => {
return of(null);
})
);
});
})
);
}
public add() {
const ip = this.form.value.ip;
this.systemService.getSwarmInfo().pipe(
switchMap((swarm: any) => {
return this.systemService.updateSwarm([{ ip }, ...swarm])
})
).subscribe({
next: () => {
this.toastr.success('Success!', 'Saved.');
},
error: (err) => {
this.toastr.error('Error.', `Could not save. ${err.message}`);
},
complete: () => {
this.form.reset();
}
});
}
}

@ -15,9 +15,9 @@ export class SystemService {
private httpClient: HttpClient
) { }
public getInfo(): Observable<ISystemInfo> {
public getInfo(ip: string = ''): Observable<ISystemInfo> {
if (environment.production) {
return this.httpClient.get(`/api/system/info`) as Observable<ISystemInfo>;
return this.httpClient.get(`${ip}/api/system/info`) as Observable<ISystemInfo>;
} else {
return of(
{
@ -100,4 +100,11 @@ export class SystemService {
}
public getSwarmInfo(): Observable<{ ip: string }[]> {
return this.httpClient.get(`/api/swarm/info`) as Observable<{ ip: string }[]>;
}
public updateSwarm(swarmConfig: any) {
return this.httpClient.patch(`/api/swarm`, swarmConfig);
}
}

@ -172,6 +172,33 @@ static esp_err_t rest_common_get_handler(httpd_req_t * req)
return ESP_OK;
}
static esp_err_t PATCH_update_swarm(httpd_req_t * req)
{
int total_len = req->content_len;
int cur_len = 0;
char * buf = ((rest_server_context_t *) (req->user_ctx))->scratch;
int received = 0;
if (total_len >= SCRATCH_BUFSIZE) {
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "content too long");
return ESP_FAIL;
}
while (cur_len < total_len) {
received = httpd_req_recv(req, buf + cur_len, total_len);
if (received <= 0) {
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to post control value");
return ESP_FAIL;
}
cur_len += received;
}
buf[total_len] = '\0';
nvs_config_set_string(NVS_CONFIG_SWARM, buf);
httpd_resp_send_chunk(req, NULL, 0);
return ESP_OK;
}
static esp_err_t PATCH_update_settings(httpd_req_t * req)
{
int total_len = req->content_len;
@ -235,6 +262,21 @@ static esp_err_t POST_restart(httpd_req_t * req)
return ESP_OK;
}
static esp_err_t GET_swarm(httpd_req_t * req)
{
httpd_resp_set_type(req, "application/json");
// Add CORS headers
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_set_hdr(req, "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
httpd_resp_set_hdr(req, "Access-Control-Allow-Headers", "Content-Type");
httpd_resp_set_hdr(req, "Access-Control-Allow-Credentials", "true");
char * swarm_config = nvs_config_get_string(NVS_CONFIG_SWARM, "[]");
httpd_resp_sendstr(req, swarm_config);
return ESP_OK;
}
/* Simple handler for getting system handler */
static esp_err_t GET_system_info(httpd_req_t * req)
{
@ -446,6 +488,7 @@ esp_err_t start_rest_server(void * pvParameters)
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.uri_match_fn = httpd_uri_match_wildcard;
config.max_uri_handlers = 20;
ESP_LOGI(TAG, "Starting HTTP Server");
REST_CHECK(httpd_start(&server, &config) == ESP_OK, "Start server failed", err_start);
@ -455,6 +498,13 @@ esp_err_t start_rest_server(void * pvParameters)
.uri = "/api/system/info", .method = HTTP_GET, .handler = GET_system_info, .user_ctx = rest_context};
httpd_register_uri_handler(server, &system_info_get_uri);
httpd_uri_t swarm_get_uri = {.uri = "/api/swarm/info", .method = HTTP_GET, .handler = GET_swarm, .user_ctx = rest_context};
httpd_register_uri_handler(server, &swarm_get_uri);
httpd_uri_t update_swarm_uri = {
.uri = "/api/swarm", .method = HTTP_PATCH, .handler = PATCH_update_swarm, .user_ctx = rest_context};
httpd_register_uri_handler(server, &update_swarm_uri);
httpd_uri_t system_restart_uri = {
.uri = "/api/system/restart", .method = HTTP_POST, .handler = POST_restart, .user_ctx = rest_context};
httpd_register_uri_handler(server, &system_restart_uri);

@ -3,6 +3,8 @@
#include <stdint.h>
// Max length 15
#define NVS_CONFIG_WIFI_SSID "wifissid"
#define NVS_CONFIG_WIFI_PASS "wifipass"
#define NVS_CONFIG_STRATUM_URL "stratumurl"
@ -21,6 +23,8 @@
#define NVS_CONFIG_FAN_SPEED "fanspeed"
#define NVS_CONFIG_BEST_DIFF "bestdiff"
#define NVS_CONFIG_SWARM "swarmconfig"
char * nvs_config_get_string(const char * key, const char * default_value);
void nvs_config_set_string(const char * key, const char * default_value);
uint16_t nvs_config_get_u16(const char * key, const uint16_t default_value);