Http-server (#17)

* rename miner to main

* serving out of storage

* axe os

* http server work

* basic stats showing

* update sdkconfig

* SDKCONFIG

* sdk config

* edit page init

* edit pool config

* pool config edit working

* OTA Success

* remove compiled output

* toggle AP mode with boot button

* favicon

* ota website update

* add sdkconfig.ci back

* update readme

* change website build to warning

* Update github workflow to build web dist

* Allow AP mode before STA connection complete

* spacing for johnny :)

* formatting

* Improve connecting to wifi with AP mode

* added working indicator for UI

* formatting

* formatting

* remove redundant sdkconfig in CMakeLists

* vs code format on save workspace settings

---------

Co-authored-by: johnny9 <985648+johnny9@users.noreply.github.com>
This commit is contained in:
Benjamin Wilson 2023-08-26 12:21:41 -04:00 committed by GitHub
parent 6d76741af8
commit 199c151c0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 15357 additions and 79 deletions

View File

@ -8,6 +8,15 @@ jobs:
uses: actions/checkout@v2
with:
submodules: 'recursive'
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Build web dist
working-directory: ./main/http_server/axe-os
run: |
npm ci
npm run build
- name: esp-idf build
uses: espressif/esp-idf-ci-action@v1
with:

9
.gitignore vendored
View File

@ -3,8 +3,13 @@ sdkconfig
sdkconfig.old
dependencies.lock
# User specific VSCode
**/.vscode/*
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Production folder
build/

17
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,17 @@
{
"idf.flashType": "UART",
"idf.portWin": "COM6",
"idf.adapterTargetName": "esp32s3",
"idf.openOcdConfigs": [
"interface/ftdi/esp32_devkitj_v1.cfg",
"target/esp32s3.cfg"
],
"files.associations": {
"freertos.h": "c",
"esp_netif.h": "c",
"esp_http_server.h": "c",
"esp_chip_info.h": "c",
"xtensa_context.h": "c"
},
"editor.formatOnSave": true
}

View File

@ -7,4 +7,7 @@ cmake_minimum_required(VERSION 3.5)
# set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(esp-miner)

View File

@ -8,12 +8,12 @@
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/lwip_napt.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "connect.h"
#include "miner.h"
#include "main.h"
#if CONFIG_ESP_WPA3_SAE_PWE_HUNT_AND_PECK
@ -59,6 +59,10 @@ static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
// Wait a little
vTaskDelay(1000 / portTICK_PERIOD_MS);
if (s_retry_num < WIFI_MAXIMUM_RETRY) {
esp_wifi_connect();
s_retry_num++;
@ -77,23 +81,53 @@ static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_
}
}
EventBits_t wifi_init_sta(const char * wifi_ssid, const char * wifi_pass) {
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
esp_netif_t *wifi_init_softap(void)
{
esp_netif_t *esp_netif_ap = esp_netif_create_default_wifi_ap();
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_config_t wifi_ap_config = {
.ap = {
.ssid = "Bitaxe",
.ssid_len = strlen("Bitaxe"),
.channel = 1,
.max_connection = 30,
.authmode = WIFI_AUTH_OPEN,
.pmf_cfg = {
.required = false,
},
},
};
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_ap_config));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip));
return esp_netif_ap;
}
wifi_config_t wifi_config = {
void toggle_wifi_softap(void){
wifi_mode_t mode = WIFI_MODE_NULL;
ESP_ERROR_CHECK(esp_wifi_get_mode(&mode));
if (mode == WIFI_MODE_APSTA) {
ESP_LOGI(TAG, "ESP_WIFI Access Point Off");
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
} else {
ESP_LOGI(TAG, "ESP_WIFI Access Point On");
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));
}
}
void wifi_softap_off(void){
ESP_LOGI(TAG, "ESP_WIFI Access Point Off");
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
}
/* Initialize wifi station */
esp_netif_t *wifi_init_sta(const char * wifi_ssid, const char * wifi_pass)
{
esp_netif_t *esp_netif_sta = esp_netif_create_default_wifi_sta();
wifi_config_t wifi_sta_config = {
.sta = {
/* Authmode threshold resets to WPA2 as default if password matches WPA2 standards (pasword len => 8).
* If you want to connect the device to deprecated WEP/WPA networks, Please set the threshold value
@ -105,18 +139,56 @@ EventBits_t wifi_init_sta(const char * wifi_ssid, const char * wifi_pass) {
// .sae_h2e_identifier = EXAMPLE_H2E_IDENTIFIER,
},
};
strncpy((char *) wifi_config.sta.ssid, wifi_ssid, 31);
wifi_config.sta.ssid[31] = '\0';
strncpy((char *) wifi_config.sta.password, wifi_pass, 63);
wifi_config.sta.password[63] = '\0';
strncpy((char *) wifi_sta_config.sta.ssid, wifi_ssid, 31);
wifi_sta_config.sta.ssid[31] = '\0';
strncpy((char *) wifi_sta_config.sta.password, wifi_pass, 63);
wifi_sta_config.sta.password[63] = '\0';
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_sta_config) );
ESP_LOGI(TAG, "wifi_init_sta finished.");
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
return esp_netif_sta;
}
void wifi_init(const char * wifi_ssid, const char * wifi_pass) {
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip));
/*Initialize WiFi */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));
/* Initialize AP */
ESP_LOGI(TAG, "ESP_WIFI Access Point On");
esp_netif_t *esp_netif_ap = wifi_init_softap();
/* Initialize STA */
ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
esp_netif_t *esp_netif_sta = wifi_init_sta(wifi_ssid, wifi_pass);
/* Start WiFi */
ESP_ERROR_CHECK(esp_wifi_start() );
ESP_LOGI(TAG, "wifi_init_sta finished.");
return;
}
EventBits_t wifi_connect(void){
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
* number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,

View File

@ -27,4 +27,7 @@ typedef enum {
WIFI_RETRYING,
} wifi_status_t;
EventBits_t wifi_init_sta(const char * ssid, const char * pass);
void toggle_wifi_softap(void);
void wifi_softap_off(void);
void wifi_init(const char * wifi_ssid, const char * wifi_pass);
EventBits_t wifi_connect(void);

View File

@ -6,7 +6,7 @@ SRCS
"fonts.c"
"INA260.c"
"led_controller.c"
"miner.c"
"main.c"
"nvs_config.c"
"oled.c"
"system.c"
@ -15,9 +15,20 @@ SRCS
"./tasks/create_jobs_task.c"
"./tasks/asic_task.c"
"./tasks/asic_result_task.c"
"./tasks/user_input_task.c"
"./tasks/power_management_task.c"
"./http_server/http_server.c"
INCLUDE_DIRS
"."
"tasks"
"http_server"
"../components/connect/include"
)
set(WEB_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/http_server/axe-os")
if(EXISTS ${WEB_SRC_DIR}/dist/axe-os)
spiffs_create_partition_image(www ${WEB_SRC_DIR}/dist/axe-os FLASH_IN_PROJECT)
else()
message(WARNING "${WEB_SRC_DIR}/dist doesn't exit. Please run 'npm run build' in ${WEB_SRC_DIR}")
endif()

View File

@ -53,4 +53,5 @@ typedef struct {
#endif /* GLOBAL_STATE_H_ */

View File

@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

42
main/http_server/axe-os/.gitignore vendored Normal file
View File

@ -0,0 +1,42 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db

View File

@ -0,0 +1,4 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
"recommendations": ["angular.ng-template"]
}

View File

@ -0,0 +1,20 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ng serve",
"type": "chrome",
"request": "launch",
"url": "http://localhost:4200/",
"sourceMaps": true
},
{
"name": "ng test",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: test",
"url": "http://localhost:9876/debug.html"
}
]
}

View File

@ -0,0 +1,5 @@
{
"cSpell.words": [
"toastr"
]
}

View File

@ -0,0 +1,42 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
},
{
"type": "npm",
"script": "test",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
}
]
}

View File

@ -0,0 +1,27 @@
# AxeOs
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.1.3.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

View File

@ -0,0 +1,111 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"axe-os": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/axe-os",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss",
"node_modules/ngx-toastr/toastr.css"
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"budgets": [
{
"type": "initial",
"maximumWarning": "750kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "axe-os:build:production"
},
"development": {
"browserTarget": "axe-os:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "axe-os:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
}
}
}
}
}

13229
main/http_server/axe-os/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,42 @@
{
"name": "axe-os",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build --configuration=production",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"bundle-report": "ng build --configuration=production --stats-json && webpack-bundle-analyzer dist/axe-os/stats.json"
},
"private": true,
"dependencies": {
"@angular/animations": "^16.1.0",
"@angular/common": "^16.1.0",
"@angular/compiler": "^16.1.0",
"@angular/core": "^16.1.0",
"@angular/forms": "^16.1.0",
"@angular/platform-browser": "^16.1.0",
"@angular/platform-browser-dynamic": "^16.1.0",
"@angular/router": "^16.1.0",
"ngx-toastr": "^17.0.2",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.13.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^16.1.3",
"@angular/cli": "~16.1.3",
"@angular/compiler-cli": "^16.1.0",
"@types/jasmine": "~4.3.0",
"gzipper": "^7.2.0",
"jasmine-core": "~4.6.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.1.3",
"webpack-bundle-analyzer": "^4.9.0"
}
}

View File

@ -0,0 +1,22 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { EditComponent } from './components/edit/edit.component';
import { HomeComponent } from './components/home/home.component';
const routes: Routes = [
{
path: '',
component: HomeComponent
},
{
path: 'edit',
component: EditComponent
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@ -0,0 +1,5 @@
<app-loading></app-loading>
<app-header></app-header>
<div class="content">
<router-outlet></router-outlet>
</div>

View File

@ -0,0 +1,14 @@
.content {
margin-top: 150px;
padding-left: 10vw;
padding-right: 10vw;
}
@media only screen and (max-width:900px) {
.content {
padding-left: 25px;
padding-right: 25px;
}
}

View File

@ -0,0 +1,29 @@
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(() => TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [AppComponent]
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'axe-os'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('axe-os');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('.content span')?.textContent).toContain('axe-os app is running!');
});
});

View File

@ -0,0 +1,15 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
constructor() {
}
}

View File

@ -0,0 +1,44 @@
import { CommonModule, HashLocationStrategy, LocationStrategy } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ToastrModule } from 'ngx-toastr';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { EditComponent } from './components/edit/edit.component';
import { HeaderComponent } from './components/header/header.component';
import { HomeComponent } from './components/home/home.component';
import { LoadingComponent } from './components/loading/loading.component';
const components = [
AppComponent,
HeaderComponent
];
@NgModule({
declarations: [
...components,
EditComponent,
HomeComponent,
LoadingComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
ReactiveFormsModule,
ToastrModule.forRoot({
positionClass: 'toast-bottom-right'
}),
BrowserAnimationsModule,
CommonModule
],
providers: [
{ provide: LocationStrategy, useClass: HashLocationStrategy },
],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -0,0 +1,48 @@
<div class="card">
<h2>Edit</h2>
<ng-container *ngIf="form != null">
<form [formGroup]="form">
<div class="form-group">
<label>WiFi SSID: </label>
<input formControlName="ssid" type="text">
</div>
<div class="form-group">
<label>WiFi Password: </label>
<input formControlName="wifiPass" type="password">
</div>
<div class="form-group">
<label>Stratum URL:</label>
<input formControlName="stratumURL" type="text">
</div>
<div class="form-group">
<label>Stratum Port:</label>
<input formControlName="stratumPort" type="number">
</div>
<div class="form-group">
<label>Stratum User: </label>
<input formControlName="stratumUser" type="text">
</div>
</form>
</ng-container>
<div class="mt-2">
<button (click)="updateSystem()" class="btn btn-primary mr-2">Save</button>
<button [routerLink]="['/']" class="btn btn-secondary">Cancel</button>
</div>
</div>
<div class="card">
<h2>Update Firmware <span *ngIf="firmwareUpdateProgress != null">{{firmwareUpdateProgress}}%</span></h2>
<input type="file" id="file" (change)="otaUpdate($event)" accept=".bin">
<br>
<small>(esp-miner.bin)</small>
</div>
<div class="card">
<h2>Update Website <span *ngIf="websiteUpdateProgress != null">{{websiteUpdateProgress}}%</span></h2>
<input type="file" id="file" (change)="otaWWWUpdate($event)" accept=".bin">
<br>
<small>(www.bin)</small>
</div>

View File

@ -0,0 +1,3 @@
input {
min-width: 500px
}

View File

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

View File

@ -0,0 +1,114 @@
import { HttpErrorResponse, HttpEventType } from '@angular/common/http';
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { LoadingService } from 'src/app/services/loading.service';
import { SystemService } from 'src/app/services/system.service';
@Component({
selector: 'app-edit',
templateUrl: './edit.component.html',
styleUrls: ['./edit.component.scss']
})
export class EditComponent {
public form!: FormGroup;
public firmwareUpdateProgress: number | null = null;
public websiteUpdateProgress: number | null = null;
constructor(
private fb: FormBuilder,
private systemService: SystemService,
private toastr: ToastrService,
private toastrService: ToastrService,
private loadingService: LoadingService
) {
this.systemService.getInfo()
.pipe(this.loadingService.lockUIUntilComplete())
.subscribe(info => {
this.form = this.fb.group({
stratumURL: [info.stratumURL, [Validators.required]],
stratumPort: [info.stratumPort, [Validators.required]],
stratumUser: [info.stratumUser, [Validators.required]],
ssid: [info.ssid, [Validators.required]],
wifiPass: [info.wifiPass, [Validators.required]],
});
});
}
public updateSystem() {
this.systemService.updateSystem(this.form.value)
.pipe(this.loadingService.lockUIUntilComplete())
.subscribe({
next: () => {
this.toastr.success('Success!', 'Saved.');
},
error: (err: HttpErrorResponse) => {
this.toastr.error('Error.', `Could not save. ${err.message}`);
}
});
}
otaUpdate(event: any) {
const file = event.target?.files.item(0) as File;
if (file.name != 'esp-miner.bin') {
this.toastrService.error('Incorrect file, looking for esp-miner.bin.', 'Error');
return;
}
this.systemService.performOTAUpdate(file)
.pipe(this.loadingService.lockUIUntilComplete())
.subscribe({
next: (event) => {
if (event.type === HttpEventType.UploadProgress) {
this.firmwareUpdateProgress = Math.round((event.loaded / (event.total as number)) * 100);
} else if (event.type === HttpEventType.Response) {
if (event.ok) {
this.toastrService.success('Firmware updated', 'Success!');
} else {
this.toastrService.error(event.statusText, 'Error');
}
}
},
error: (err) => {
this.toastrService.error(event.statusText, 'Error');
},
complete: () => {
this.firmwareUpdateProgress = null;
}
});
}
otaWWWUpdate(event: any) {
const file = event.target?.files.item(0) as File;
if (file.name != 'www.bin') {
this.toastrService.error('Incorrect file, looking for www.bin.', 'Error');
return;
}
this.systemService.performWWWOTAUpdate(file)
.pipe(this.loadingService.lockUIUntilComplete())
.subscribe({
next: (event) => {
if (event.type === HttpEventType.UploadProgress) {
this.websiteUpdateProgress = Math.round((event.loaded / (event.total as number)) * 100);
} else if (event.type === HttpEventType.Response) {
if (event.ok) {
this.toastrService.success('Website updated', 'Success!');
window.location.reload();
} else {
this.toastrService.error(event.statusText, 'Error');
}
}
},
error: (err) => {
this.toastrService.error(event.statusText, 'Error');
},
complete: () => {
this.websiteUpdateProgress = null;
}
});
}
}

View File

@ -0,0 +1 @@
<div [routerLink]="['/']" class="header-text">AxeOS</div>

View File

@ -0,0 +1,16 @@
:host {
height: 70px;
background-color: #1f2d40;
border-bottom: 1px solid #304562;
position: fixed;
top: 0;
right: 0;
left: 0;
}
.header-text {
font-size: 30pt;
padding: 6px;
margin-left: 10px;
cursor: pointer;
}

View File

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

View File

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
export class HeaderComponent {
}

View File

@ -0,0 +1,116 @@
<ng-container *ngIf="info$ | async as info">
<div class="button-row">
<button [routerLink]="['edit']" class="btn btn-primary edit">Edit</button>
<button (click)="restart()" class="btn btn-danger restart">Restart</button>
</div>
<div class="card">
<h2>Pool Information</h2>
<table>
<tr>
<td>URL:</td>
<td>{{info.stratumURL}}</td>
</tr>
<tr>
<td>Port:</td>
<td>{{info.stratumPort}}</td>
</tr>
<tr>
<td>User:</td>
<td>{{info.stratumUser}}</td>
</tr>
<tr>
<td>Shares Accepted:</td>
<td>{{info.sharesAccepted}}</td>
</tr>
<tr>
<td>Shares Rejected:</td>
<td>{{info.sharesRejected}}</td>
</tr>
</table>
</div>
<div class="container">
<div>
<div class="card">
<h2>Overview</h2>
<table>
<tr>
<td>Model:</td>
<td>{{info.ASICModel}}</td>
</tr>
<tr>
<td>Uptime (seconds):</td>
<td>{{info.uptimeSeconds}}</td>
</tr>
<tr>
<td>WiFi Status:</td>
<td>{{info.wifiStatus}}</td>
</tr>
<tr>
<td>Free Heap Memory:</td>
<td>{{info.freeHeap}}</td>
</tr>
</table>
</div>
</div>
<div>
<div class="card">
<h2>Power</h2>
<table>
<tr>
<td>Power Consumption:</td>
<td>{{info.power | number: '1.2-2'}} <small>W</small></td>
</tr>
<tr>
<td>Input Voltage:</td>
<td>{{info.voltage | number: '0.0-0'}} <small>mV</small></td>
</tr>
<tr>
<td>Input Current:</td>
<td>{{info.current | number: '0.0-0'}} <small>mA</small></td>
</tr>
<tr>
<td>Core Voltage:</td>
<td>{{info.coreVoltage}} <small>mV</small></td>
</tr>
<tr>
<td>Fan Speed:</td>
<td>{{info.fanSpeed}} <small>RPM</small></td>
</tr>
</table>
</div>
<div class="card">
<h2>Results</h2>
<table>
<tr>
<td> Hash Rate:</td>
<td>{{info.hashRate | number: '1.2-2'}}Gh/s</td>
</tr>
<tr>
<td>Best Difficulty:</td>
<td>{{info.bestDiff}}</td>
</tr>
</table>
</div>
</div>
</div>
</ng-container>

View File

@ -0,0 +1,39 @@
.container {
display: grid;
grid-template-columns: 2fr 2fr;
grid-gap: 1.5rem;
}
table>tr>td {
&:first-child {
text-align: right;
font-weight: bold;
}
&:nth-child(2) {
padding-left: 10px;
}
}
@media only screen and (max-width:900px) {
.container {
grid-template-columns: 1fr;
}
}
.edit {
margin-bottom: 1.5rem;
}
.restart {
margin-bottom: 1.5rem;
}
.button-row {
display: flex;
justify-content: space-between;
}

View File

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

View File

@ -0,0 +1,33 @@
import { Component } from '@angular/core';
import { ToastrService } from 'ngx-toastr';
import { Observable } from 'rxjs';
import { LoadingService } from 'src/app/services/loading.service';
import { SystemService } from 'src/app/services/system.service';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent {
public info$: Observable<any>;
constructor(
private systemService: SystemService,
private toastr: ToastrService,
private loadingService: LoadingService
) {
this.info$ = this.systemService.getInfo().pipe(
this.loadingService.lockUIUntilComplete()
)
}
public restart() {
this.systemService.restart().subscribe(res => {
});
this.toastr.success('Success!', 'Bitaxe restarted');
}
}

View File

@ -0,0 +1,3 @@
<div id="loading" *ngIf="loading$ | async">
<div id="loading-text">Working...</div>
</div>

View File

@ -0,0 +1,16 @@
#loading {
background-color: rgba(0, 0, 0, 0.5);
pointer-events: none;
top: 0;
bottom: 0;
left: 0;
right: 0;
position: fixed;
z-index: 999;
}
#loading-text {
text-align: center;
margin-top: 40vh;
font-size: 20pt;
}

View File

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

View File

@ -0,0 +1,17 @@
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { LoadingService } from 'src/app/services/loading.service';
@Component({
selector: 'app-loading',
templateUrl: './loading.component.html',
styleUrls: ['./loading.component.scss']
})
export class LoadingComponent {
public loading$: Observable<boolean>;
constructor(private loadingService: LoadingService) {
this.loading$ = this.loadingService.loading$.asObservable();
}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { LoadingService } from './loading.service';
describe('LoadingService', () => {
let service: LoadingService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(LoadingService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,33 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class LoadingService {
public loading$: BehaviorSubject<boolean> = new BehaviorSubject(false);
constructor() { }
public lockUIUntilComplete() {
return <T>(source: Observable<T>): Observable<T> => {
return new Observable(subscriber => {
this.loading$.next(true);
source.subscribe({
next: (value) => {
subscriber.next(value);
},
error: (err) => {
subscriber.next(err);
},
complete: () => {
this.loading$.next(false);
subscriber.complete();
}
})
});
}
}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { SystemService } from './system.service';
describe('SystemService', () => {
let service: SystemService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(SystemService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,93 @@
import { HttpClient, HttpEvent } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { delay, Observable, of } from 'rxjs';
import { ISystemInfo } from 'src/models/ISystemInfo';
import { environment } from '../../environments/environment';
@Injectable({
providedIn: 'root'
})
export class SystemService {
constructor(
private httpClient: HttpClient
) { }
public getInfo(): Observable<ISystemInfo> {
if (environment.production) {
return this.httpClient.get(`/api/system/info`) as Observable<ISystemInfo>;
} else {
return of(
{
"power": 11.670000076293945,
"voltage": 5208.75,
"current": 2237.5,
"fanSpeed": 82,
"hashRate": 0,
"bestDiff": "0",
"freeHeap": 200504,
"coreVoltage": 1188,
"ssid": "skimadtrees-secure",
"wifiPass": "password",
"wifiStatus": "Connected!",
"sharesAccepted": 1,
"sharesRejected": 0,
"uptimeSeconds": 38,
"ASICModel": "BM1366",
"stratumURL": "192.168.1.242",
"stratumPort": 3333,
"stratumUser": "bc1q99n3pu025yyu0jlywpmwzalyhm36tg5u37w20d.bitaxe-U1"
}
).pipe(delay(1000));
}
}
public restart() {
return this.httpClient.post(`/api/system/restart`, {});
}
public updateSystem(update: any) {
return this.httpClient.patch(`/api/system`, update);
}
private otaUpdate(file: File, url: string) {
return new Observable<HttpEvent<string>>((subscriber) => {
const reader = new FileReader();
reader.onload = (event: any) => {
const fileContent = event.target.result;
return this.httpClient.post(url, fileContent, {
reportProgress: true,
observe: 'events',
responseType: 'text', // Specify the response type
headers: {
'Content-Type': 'application/octet-stream', // Set the content type
},
}).subscribe({
next: (e) => {
subscriber.next(e)
},
error: (err) => {
subscriber.error(err)
},
complete: () => {
subscriber.complete();
}
});
};
reader.readAsArrayBuffer(file);
});
}
public performOTAUpdate(file: File) {
return this.otaUpdate(file, `/api/system/OTA`);
}
public performWWWOTAUpdate(file: File) {
return this.otaUpdate(file, `/api/system/OTAWWW`);
}
}

View File

@ -0,0 +1,3 @@
export const environment = {
production: true
};

View File

@ -0,0 +1,3 @@
export const environment = {
production: false
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>AxeOs</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@ -0,0 +1,7 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));

View File

@ -0,0 +1,21 @@
export interface ISystemInfo {
power: number,
voltage: number,
current: number,
fanSpeed: number,
hashRate: number,
bestDiff: string,
freeHeap: number,
coreVoltage: number,
ssid: string,
wifiPass: string,
wifiStatus: string,
sharesAccepted: number,
sharesRejected: number,
uptimeSeconds: number,
ASICModel: string,
stratumURL: string,
stratumPort: number,
stratumUser: string
}

View File

@ -0,0 +1,161 @@
/* You can add global styles to this file, and also import other style files */
body {
background-color: #17212f;
margin: 0;
color: white;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
}
.card {
background-color: #1f2d40;
border: 1px solid #304562;
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 1.5rem;
h1,
h2,
h3,
h4 {
margin-top: 0;
}
line-break: anywhere;
}
button {
margin: 0;
display: inline-flex;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
align-items: center;
vertical-align: bottom;
text-align: center;
overflow: hidden;
position: relative;
padding: 0.5rem 1rem;
font-size: 1rem;
transition: background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s;
border-radius: 3px;
&.btn-primary {
color: #212529;
background: #64B5F6;
border: 1px solid #64B5F6;
&:hover {
background: #43a5f4;
color: #212529;
border-color: #43a5f4;
}
}
&.btn-danger {
color: #121212;
background: #F48FB1;
border: 1px solid #F48FB1;
&:hover {
background: #f16c98;
color: #121212;
border-color: #f16c98;
}
}
&.btn-secondary {
color: #ffffff;
background: #78909C;
border: 1px solid #78909C;
&:hover {
background: #69838f;
color: #ffffff;
border-color: #69838f;
}
}
&.btn-warning {
color: #121212;
background: #FFE082;
border: 1px solid #FFE082;
&:hover {
background: #ffd65c;
color: #121212;
border-color: #ffd65c;
}
}
}
input {
font-size: 1rem;
color: rgba(255, 255, 255, 0.87);
background: #17212f;
padding: 0.5rem 0.5rem;
border: 1px solid #304562;
transition: background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s;
appearance: none;
border-radius: 3px;
}
label {
margin-right: 0.5rem;
}
.form-group {
margin-bottom: 0.5rem;
label {
margin-bottom: 0.25rem;
}
label,
input {
display: block;
}
}
.mr-2 {
margin-right: 2rem;
}
.ml-2 {
margin-left: 2rem;
}
.mt-2 {
margin-top: 2rem;
}
.mb-2 {
margin-bottom: 2rem;
}
.p-fileupload-choose {
margin: 0;
display: inline-flex;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
align-items: center;
vertical-align: bottom;
text-align: center;
overflow: hidden;
position: relative;
padding: 0.5rem 1rem;
font-size: 1rem;
transition: background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s;
border-radius: 3px;
color: #212529;
background: #64B5F6;
border: 1px solid #64B5F6;
}

View File

@ -0,0 +1,14 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts"
],
"include": [
"src/**/*.d.ts"
]
}

View File

@ -0,0 +1,33 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,
"lib": [
"ES2022",
"dom"
]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View File

@ -0,0 +1,14 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
},
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View File

@ -0,0 +1,419 @@
#include "http_server.h"
#include "global_state.h"
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include <string.h>
#include <fcntl.h>
#include "esp_http_server.h"
#include "esp_chip_info.h"
#include "esp_random.h"
#include "esp_log.h"
#include "esp_vfs.h"
#include "cJSON.h"
#include "esp_spiffs.h"
#include "esp_log.h"
#include "adc.h"
#include "esp_timer.h"
#include "nvs_config.h"
#include "esp_netif.h"
#include "esp_mac.h"
#include "esp_wifi.h"
#include "lwip/inet.h"
#include "lwip/netdb.h"
#include "lwip/sockets.h"
#include "lwip/lwip_napt.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "esp_ota_ops.h"
static const char *TAG = "http_server";
static GlobalState *GLOBAL_STATE;
#define REST_CHECK(a, str, goto_tag, ...) \
do \
{ \
if (!(a)) \
{ \
ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
goto goto_tag; \
} \
} while (0)
#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + 128)
#define SCRATCH_BUFSIZE (10240)
typedef struct rest_server_context {
char base_path[ESP_VFS_PATH_MAX + 1];
char scratch[SCRATCH_BUFSIZE];
} rest_server_context_t;
#define CHECK_FILE_EXTENSION(filename, ext) (strcasecmp(&filename[strlen(filename) - strlen(ext)], ext) == 0)
esp_err_t init_fs(void)
{
esp_vfs_spiffs_conf_t conf = {
.base_path = "",
.partition_label = NULL,
.max_files = 5,
.format_if_mount_failed = false
};
esp_err_t ret = esp_vfs_spiffs_register(&conf);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "Failed to mount or format filesystem");
} else if (ret == ESP_ERR_NOT_FOUND) {
ESP_LOGE(TAG, "Failed to find SPIFFS partition");
} else {
ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
}
return ESP_FAIL;
}
size_t total = 0, used = 0;
ret = esp_spiffs_info(NULL, &total, &used);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret));
} else {
ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
}
return ESP_OK;
}
/* Function for stopping the webserver */
void stop_webserver(httpd_handle_t server)
{
if (server) {
/* Stop the httpd server */
httpd_stop(server);
}
}
/* Set HTTP response content type according to file extension */
static esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filepath)
{
const char *type = "text/plain";
if (CHECK_FILE_EXTENSION(filepath, ".html")) {
type = "text/html";
} else if (CHECK_FILE_EXTENSION(filepath, ".js")) {
type = "application/javascript";
} else if (CHECK_FILE_EXTENSION(filepath, ".css")) {
type = "text/css";
} else if (CHECK_FILE_EXTENSION(filepath, ".png")) {
type = "image/png";
} else if (CHECK_FILE_EXTENSION(filepath, ".ico")) {
type = "image/x-icon";
} else if (CHECK_FILE_EXTENSION(filepath, ".svg")) {
type = "text/xml";
}
return httpd_resp_set_type(req, type);
}
/* Send HTTP response with the contents of the requested file */
static esp_err_t rest_common_get_handler(httpd_req_t *req)
{
char filepath[FILE_PATH_MAX];
rest_server_context_t *rest_context = (rest_server_context_t *)req->user_ctx;
strlcpy(filepath, rest_context->base_path, sizeof(filepath));
if (req->uri[strlen(req->uri) - 1] == '/') {
strlcat(filepath, "/index.html", sizeof(filepath));
} else {
strlcat(filepath, req->uri, sizeof(filepath));
}
int fd = open(filepath, O_RDONLY, 0);
if (fd == -1) {
ESP_LOGE(TAG, "Failed to open file : %s", filepath);
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to read existing file");
return ESP_FAIL;
}
set_content_type_from_file(req, filepath);
char *chunk = rest_context->scratch;
ssize_t read_bytes;
do {
/* Read file in chunks into the scratch buffer */
read_bytes = read(fd, chunk, SCRATCH_BUFSIZE);
if (read_bytes == -1) {
ESP_LOGE(TAG, "Failed to read file : %s", filepath);
} else if (read_bytes > 0) {
/* Send the buffer contents as HTTP response chunk */
if (httpd_resp_send_chunk(req, chunk, read_bytes) != ESP_OK) {
close(fd);
ESP_LOGE(TAG, "File sending failed!");
/* Abort sending file */
httpd_resp_sendstr_chunk(req, NULL);
/* Respond with 500 Internal Server Error */
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file");
return ESP_FAIL;
}
}
} while (read_bytes > 0);
/* Close file after sending complete */
close(fd);
ESP_LOGI(TAG, "File sending complete");
/* Respond with an empty chunk to signal HTTP response completion */
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;
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';
cJSON *root = cJSON_Parse(buf);
char *stratumURL = cJSON_GetObjectItem(root, "stratumURL")->valuestring;
char *stratumUser = cJSON_GetObjectItem(root, "stratumUser")->valuestring;
uint16_t stratumPort = cJSON_GetObjectItem(root, "stratumPort")->valueint;
char *ssid = cJSON_GetObjectItem(root, "ssid")->valuestring;
char *wifiPass = cJSON_GetObjectItem(root, "wifiPass")->valuestring;
nvs_config_set_string(NVS_CONFIG_STRATUM_URL, stratumURL);
nvs_config_set_string(NVS_CONFIG_STRATUM_USER, stratumUser);
nvs_config_set_u16(NVS_CONFIG_STRATUM_PORT, stratumPort);
nvs_config_set_string(NVS_CONFIG_WIFI_SSID, ssid);
nvs_config_set_string(NVS_CONFIG_WIFI_PASS, wifiPass);
cJSON_Delete(root);
httpd_resp_send_chunk(req, NULL, 0);
return ESP_OK;
}
static esp_err_t POST_restart(httpd_req_t *req)
{
esp_restart();
return ESP_OK;
}
/* Simple handler for getting system handler */
static esp_err_t GET_system_info(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");
cJSON *root = cJSON_CreateObject();
cJSON_AddNumberToObject(root, "power", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.power);
cJSON_AddNumberToObject(root, "voltage", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.voltage);
cJSON_AddNumberToObject(root, "current", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.current);
cJSON_AddNumberToObject(root, "fanSpeed", GLOBAL_STATE->POWER_MANAGEMENT_MODULE.fan_speed);
cJSON_AddNumberToObject(root, "hashRate", GLOBAL_STATE->SYSTEM_MODULE.current_hashrate);
cJSON_AddStringToObject(root, "bestDiff", GLOBAL_STATE->SYSTEM_MODULE.best_diff_string);
cJSON_AddNumberToObject(root, "freeHeap", esp_get_free_heap_size());
cJSON_AddNumberToObject(root, "coreVoltage", ADC_get_vcore());
cJSON_AddStringToObject(root, "ssid", nvs_config_get_string(NVS_CONFIG_WIFI_SSID, CONFIG_ESP_WIFI_SSID));
cJSON_AddStringToObject(root, "wifiPass", nvs_config_get_string(NVS_CONFIG_WIFI_PASS, CONFIG_ESP_WIFI_PASSWORD));
cJSON_AddStringToObject(root, "wifiStatus", GLOBAL_STATE->SYSTEM_MODULE.wifi_status);
cJSON_AddNumberToObject(root, "sharesAccepted", GLOBAL_STATE->SYSTEM_MODULE.shares_accepted);
cJSON_AddNumberToObject(root, "sharesRejected", GLOBAL_STATE->SYSTEM_MODULE.shares_rejected);
cJSON_AddNumberToObject(root, "uptimeSeconds", (esp_timer_get_time() - GLOBAL_STATE->SYSTEM_MODULE.start_time)/1000000);
cJSON_AddStringToObject(root, "ASICModel", CONFIG_ASIC_MODEL);
cJSON_AddStringToObject(root, "stratumURL", nvs_config_get_string(NVS_CONFIG_STRATUM_URL, CONFIG_STRATUM_URL));
cJSON_AddNumberToObject(root, "stratumPort", nvs_config_get_u16(NVS_CONFIG_STRATUM_PORT, CONFIG_STRATUM_PORT));
cJSON_AddStringToObject(root, "stratumUser", nvs_config_get_string(NVS_CONFIG_STRATUM_USER, CONFIG_STRATUM_USER));
const char *sys_info = cJSON_Print(root);
httpd_resp_sendstr(req, sys_info);
free((void *)sys_info);
cJSON_Delete(root);
return ESP_OK;
}
esp_err_t POST_WWW_update(httpd_req_t *req)
{
char buf[1000];
int remaining = req->content_len;
const esp_partition_t *www_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, "www");
if (www_partition == NULL) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "WWW partition not found");
return ESP_FAIL;
}
// Erase the entire www partition before writing
ESP_ERROR_CHECK(esp_partition_erase_range(www_partition, 0, www_partition->size));
while (remaining > 0) {
int recv_len = httpd_req_recv(req, buf, MIN(remaining, sizeof(buf)));
if (recv_len == HTTPD_SOCK_ERR_TIMEOUT) {
continue;
} else if (recv_len <= 0) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Protocol Error");
return ESP_FAIL;
}
if (esp_partition_write(www_partition, www_partition->size - remaining, (const void *)buf, recv_len) != ESP_OK) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Write Error");
return ESP_FAIL;
}
remaining -= recv_len;
}
httpd_resp_sendstr(req, "WWW update complete\n");
return ESP_OK;
}
/*
* Handle OTA file upload
*/
esp_err_t POST_OTA_update(httpd_req_t *req)
{
char buf[1000];
esp_ota_handle_t ota_handle;
int remaining = req->content_len;
const esp_partition_t *ota_partition = esp_ota_get_next_update_partition(NULL);
ESP_ERROR_CHECK(esp_ota_begin(ota_partition, OTA_SIZE_UNKNOWN, &ota_handle));
while (remaining > 0) {
int recv_len = httpd_req_recv(req, buf, MIN(remaining, sizeof(buf)));
// Timeout Error: Just retry
if (recv_len == HTTPD_SOCK_ERR_TIMEOUT) {
continue;
// Serious Error: Abort OTA
} else if (recv_len <= 0) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Protocol Error");
return ESP_FAIL;
}
// Successful Upload: Flash firmware chunk
if (esp_ota_write(ota_handle, (const void *)buf, recv_len) != ESP_OK) {
esp_ota_abort(ota_handle);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Flash Error");
return ESP_FAIL;
}
remaining -= recv_len;
}
// Validate and switch to new OTA image and reboot
if (esp_ota_end(ota_handle) != ESP_OK || esp_ota_set_boot_partition(ota_partition) != ESP_OK) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Validation / Activation Error");
return ESP_FAIL;
}
httpd_resp_sendstr(req, "Firmware update complete, rebooting now!\n");
vTaskDelay(500 / portTICK_PERIOD_MS);
esp_restart();
return ESP_OK;
}
esp_err_t start_rest_server(void *pvParameters)
{
GLOBAL_STATE = (GlobalState*)pvParameters;
const char *base_path = "";
ESP_ERROR_CHECK(init_fs());
REST_CHECK(base_path, "wrong base path", err);
rest_server_context_t *rest_context = calloc(1, sizeof(rest_server_context_t));
REST_CHECK(rest_context, "No memory for rest context", err);
strlcpy(rest_context->base_path, base_path, sizeof(rest_context->base_path));
httpd_handle_t server = NULL;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.uri_match_fn = httpd_uri_match_wildcard;
ESP_LOGI(TAG, "Starting HTTP Server");
REST_CHECK(httpd_start(&server, &config) == ESP_OK, "Start server failed", err_start);
/* URI handler for fetching system info */
httpd_uri_t system_info_get_uri = {
.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 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);
httpd_uri_t update_system_settings_uri = {
.uri = "/api/system",
.method = HTTP_PATCH,
.handler = PATCH_update_settings,
.user_ctx = rest_context
};
httpd_register_uri_handler(server, &update_system_settings_uri);
httpd_uri_t update_post_ota_firmware = {
.uri = "/api/system/OTA",
.method = HTTP_POST,
.handler = POST_OTA_update,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &update_post_ota_firmware);
httpd_uri_t update_post_ota_www = {
.uri = "/api/system/OTAWWW",
.method = HTTP_POST,
.handler = POST_WWW_update,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &update_post_ota_www);
/* URI handler for getting web server files */
httpd_uri_t common_get_uri = {
.uri = "/*",
.method = HTTP_GET,
.handler = rest_common_get_handler,
.user_ctx = rest_context
};
httpd_register_uri_handler(server, &common_get_uri);
return ESP_OK;
err_start:
free(rest_context);
err:
return ESP_FAIL;
}

View File

@ -0,0 +1,7 @@
#ifndef HTTP_SERVER_H_
#define HTTP_SERVER_H_
#include <esp_http_server.h>
esp_err_t start_rest_server(void *pvParameters);
#endif

6
main/http_server/package-lock.json generated Normal file
View File

@ -0,0 +1,6 @@
{
"name": "http_server",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

35
main/miner.c → main/main.c Executable file → Normal file
View File

@ -4,7 +4,7 @@
#include "nvs_flash.h"
//#include "protocol_examples_common.h"
#include "miner.h"
#include "main.h"
#include "stratum_task.h"
@ -14,11 +14,15 @@
#include "serial.h"
#include "asic_result_task.h"
#include "nvs_config.h"
#include "http_server.h"
#include "esp_netif.h"
#include "user_input_task.h"
#define ASIC_MODEL CONFIG_ASIC_MODEL
static GlobalState GLOBAL_STATE = {
.extranonce_str = NULL,
.extranonce_2_len = 0,
@ -64,9 +68,6 @@ void app_main(void)
exit(EXIT_FAILURE);
}
ESP_LOGI(TAG, "Welcome to the bitaxe!");
//wait between 0 and 5 seconds for multiple units
vTaskDelay(rand() % 5001 / portTICK_PERIOD_MS);
@ -74,8 +75,6 @@ void app_main(void)
xTaskCreate(SYSTEM_task, "SYSTEM_task", 4096, (void*)&GLOBAL_STATE, 3, NULL);
ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
//pull the wifi credentials out of NVS
char * wifi_ssid = nvs_config_get_string(NVS_CONFIG_WIFI_SSID, WIFI_SSID);
char * wifi_pass = nvs_config_get_string(NVS_CONFIG_WIFI_PASS, WIFI_PASS);
@ -84,7 +83,9 @@ void app_main(void)
strncpy(GLOBAL_STATE.SYSTEM_MODULE.ssid, wifi_ssid, 20);
//init and connect to wifi
EventBits_t result_bits = wifi_init_sta(wifi_ssid, wifi_pass);
wifi_init(wifi_ssid, wifi_pass);
start_rest_server((void*)&GLOBAL_STATE);
EventBits_t result_bits = wifi_connect();
if (result_bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "Connected to SSID: %s", wifi_ssid);
@ -92,13 +93,23 @@ void app_main(void)
} else if (result_bits & WIFI_FAIL_BIT) {
ESP_LOGE(TAG, "Failed to connect to SSID: %s", wifi_ssid);
strncpy(GLOBAL_STATE.SYSTEM_MODULE.wifi_status, "Failed to connect", 20);
esp_restart(); //this is pretty much fatal, so just restart
// User might be trying to configure with AP, just chill here
ESP_LOGI(TAG,"Finished, waiting for user input.");
while(1){
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
strncpy(GLOBAL_STATE.SYSTEM_MODULE.wifi_status, "unexpected error", 20);
esp_restart(); //this is pretty much fatal, so just restart
// User might be trying to configure with AP, just chill here
ESP_LOGI(TAG,"Finished, waiting for user input.");
while(1){
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
wifi_softap_off();
free(wifi_ssid);
free(wifi_pass);
@ -118,6 +129,9 @@ void app_main(void)
xTaskCreate(ASIC_task, "asic", 8192, (void*)&GLOBAL_STATE, 10, NULL);
xTaskCreate(ASIC_result_task, "asic result", 8192, (void*)&GLOBAL_STATE, 15, NULL);
xTaskCreate(USER_INPUT_task, "user input", 8192, (void*)&GLOBAL_STATE, 5, NULL);
}
void MINER_set_wifi_status(wifi_status_t status, uint16_t retry_count) {
@ -129,4 +143,3 @@ void MINER_set_wifi_status(wifi_status_t status, uint16_t retry_count) {
return;
}
}

View File

@ -1,8 +1,8 @@
#ifndef MINER_H_
#define MINER_H_
#ifndef MAIN_H_
#define MAIN_H_
#include "connect.h"
void MINER_set_wifi_status(wifi_status_t status, uint16_t retry_count);
#endif /* MINER_H_ */
#endif /* MAIN_H_ */

View File

@ -40,6 +40,29 @@ char * nvs_config_get_string(const char * key, const char * default_value)
return out;
}
void nvs_config_set_string(const char * key, const char * value)
{
nvs_handle handle;
esp_err_t err;
err = nvs_open(NVS_CONFIG_NAMESPACE, NVS_READWRITE, &handle);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "Could not open nvs");
return;
}
err = nvs_set_str(handle, key, value);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "Could not write nvs key: %s, value: %s", key, value);
return;
}
nvs_close(handle);
return;
}
uint16_t nvs_config_get_u16(const char * key, const uint16_t default_value)
{
nvs_handle handle;
@ -63,3 +86,26 @@ uint16_t nvs_config_get_u16(const char * key, const uint16_t default_value)
nvs_close(handle);
return out;
}
void nvs_config_set_u16(const char * key, const uint16_t value)
{
nvs_handle handle;
esp_err_t err;
err = nvs_open(NVS_CONFIG_NAMESPACE, NVS_READWRITE, &handle);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "Could not open nvs");
return;
}
err = nvs_set_u16(handle, key, value);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "Could not write nvs key: %s, value: %u", key, value);
return;
}
nvs_close(handle);
return;
}

View File

@ -14,6 +14,8 @@
#define NVS_CONFIG_ASIC_MODEL "asicModel"
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);
void nvs_config_set_u16(const char * key, const uint16_t value);
#endif // MAIN_NVS_CONFIG_H

View File

@ -20,104 +20,105 @@
#define VOLTAGE_RANGE (VOLTAGE_START_THROTTLE - VOLTAGE_MIN_THROTTLE)
#define ASIC_MODEL CONFIG_ASIC_MODEL
static const char * TAG = "power_management";
static const char *TAG = "power_management";
static float _fbound(float value, float lower_bound, float upper_bound)
{
if (value < lower_bound)
return lower_bound;
if (value > upper_bound)
return upper_bound;
if (value < lower_bound)
return lower_bound;
if (value > upper_bound)
return upper_bound;
return value;
return value;
}
// void _power_init(PowerManagementModule * power_management){
// power_management->frequency_multiplier = 1;
// power_management->frequency_value = ASIC_FREQUENCY;
void POWER_MANAGEMENT_task(void *pvParameters)
{
// }
GlobalState *GLOBAL_STATE = (GlobalState *)pvParameters;
void POWER_MANAGEMENT_task(void * pvParameters){
GlobalState *GLOBAL_STATE = (GlobalState*)pvParameters;
//bm1397Module * bm1397 = &GLOBAL_STATE->BM1397_MODULE;
PowerManagementModule * power_management = &GLOBAL_STATE->POWER_MANAGEMENT_MODULE;
// _power_init(power_management);
PowerManagementModule *power_management = &GLOBAL_STATE->POWER_MANAGEMENT_MODULE;
int last_frequency_increase = 0;
while(1){
while (1)
{
power_management->voltage = INA260_read_voltage();
power_management->power = INA260_read_power() / 1000;
power_management->current = INA260_read_current();
power_management->fan_speed = EMC2101_get_fan_speed();
if(strcmp(ASIC_MODEL, "BM1397") == 0){
if (strcmp(ASIC_MODEL, "BM1397") == 0)
{
power_management->chip_temp = EMC2101_get_chip_temp();
// Voltage
// We'll throttle between 4.9v and 3.5v
float voltage_multiplier = _fbound((power_management->voltage - VOLTAGE_MIN_THROTTLE) * (1/(float)VOLTAGE_RANGE), 0, 1);
float voltage_multiplier = _fbound((power_management->voltage - VOLTAGE_MIN_THROTTLE) * (1 / (float)VOLTAGE_RANGE), 0, 1);
// Temperature
float temperature_multiplier = 1;
float over_temp = -(THROTTLE_TEMP - power_management->chip_temp);
if(over_temp > 0){
temperature_multiplier = (THROTTLE_TEMP_RANGE - over_temp)/THROTTLE_TEMP_RANGE;
if (over_temp > 0)
{
temperature_multiplier = (THROTTLE_TEMP_RANGE - over_temp) / THROTTLE_TEMP_RANGE;
}
float lowest_multiplier = 1;
float multipliers[2] = {voltage_multiplier, temperature_multiplier};
for(int i = 0; i < 2; i++){
if(multipliers[i] < lowest_multiplier){
for (int i = 0; i < 2; i++)
{
if (multipliers[i] < lowest_multiplier)
{
lowest_multiplier = multipliers[i];
}
}
power_management->frequency_multiplier = lowest_multiplier;
float target_frequency = _fbound(power_management->frequency_multiplier * ASIC_FREQUENCY, 0, ASIC_FREQUENCY);
if(target_frequency < 50){
if (target_frequency < 50)
{
// TODO: Turn the chip off
}
// chip is coming back from a low/no voltage event
if(power_management->frequency_value < 50 && target_frequency > 50){
if (power_management->frequency_value < 50 && target_frequency > 50)
{
// TODO recover gracefully?
esp_restart();
}
if(power_management->frequency_value > target_frequency){
if (power_management->frequency_value > target_frequency)
{
power_management->frequency_value = target_frequency;
last_frequency_increase = 0;
BM1397_send_hash_frequency(power_management->frequency_value);
ESP_LOGI(TAG, "target %f, Freq %f, Temp %f, Power %f", target_frequency, power_management->frequency_value, power_management->chip_temp, power_management->power);
}else{
if(
}
else
{
if (
last_frequency_increase > 120 &&
power_management->frequency_value != ASIC_FREQUENCY
){
power_management->frequency_value != ASIC_FREQUENCY)
{
float add = (target_frequency + power_management->frequency_value) / 2;
power_management->frequency_value += _fbound(add, 2 , 20);
power_management->frequency_value += _fbound(add, 2, 20);
BM1397_send_hash_frequency(power_management->frequency_value);
ESP_LOGI(TAG, "target %f, Freq %f, Temp %f, Power %f", target_frequency, power_management->frequency_value, power_management->chip_temp, power_management->power);
last_frequency_increase = 60;
}else{
}
else
{
last_frequency_increase++;
}
}
}
//ESP_LOGI(TAG, "target %f, Freq %f, Volt %f, Power %f", target_frequency, power_management->frequency_value, power_management->voltage, power_management->power);
// ESP_LOGI(TAG, "target %f, Freq %f, Volt %f, Power %f", target_frequency, power_management->frequency_value, power_management->voltage, power_management->power);
vTaskDelay(POLL_RATE / portTICK_PERIOD_MS);
}
}

View File

@ -0,0 +1,27 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "connect.h"
#define BUTTON_BOOT GPIO_NUM_0
static const char * TAG = "user_input";
static bool button_being_pressed = false;
void USER_INPUT_task(void * pvParameters){
gpio_set_direction(BUTTON_BOOT, GPIO_MODE_INPUT);
while(1) {
if (gpio_get_level(BUTTON_BOOT) == 0 && button_being_pressed == false) { // If button is pressed
ESP_LOGI(TAG, "BUTTON PRESSED");
button_being_pressed = true;
toggle_wifi_softap();
} else if (gpio_get_level(BUTTON_BOOT) == 1 && button_being_pressed == true){
button_being_pressed = false;
}
vTaskDelay(30/portTICK_PERIOD_MS); // Add delay so that current task does not starve idle task and trigger watchdog timer
}
}

View File

@ -0,0 +1,7 @@
#ifndef USER_INPUT_TASK_H_
#define USER_INPUT_TASK_H_
void USER_INPUT_task(void * pvParameters);
#endif

9
partitions.csv Normal file
View File

@ -0,0 +1,9 @@
# Name, Type, SubType, Offset, Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, 0x9000, 0x6000
phy_init, data, phy, 0xf000, 0x1000
factory, app, factory, 0x10000, 1M
www, data, spiffs, 0x110000, 2M
ota_0, app, ota_0, 0x310000, 1M
ota_1, app, ota_1, 0x410000, 1M
otadata, data, ota, 0x510000, 8k
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, 0x9000, 0x6000
4 phy_init, data, phy, 0xf000, 0x1000
5 factory, app, factory, 0x10000, 1M
6 www, data, spiffs, 0x110000, 2M
7 ota_0, app, ota_0, 0x310000, 1M
8 ota_1, app, ota_1, 0x410000, 1M
9 otadata, data, ota, 0x510000, 8k

View File

@ -51,6 +51,24 @@ Set following parameters under Example Connection Configuration Options:
For more information about the example_connect() method used here, check out <https://github.com/espressif/esp-idf/blob/master/examples/protocols/README.md>.
## Build website
To build the website for viewing and OTA updates open the Angular project found in
```
ESP-Miner\main\http_server\axe-os
```
Then install dependencies and build.
```
npm i
npm run build
```
When the esp-idf project is built it will bundle the website in www.bin
## Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:

0
sdkconfig.ci Executable file → Normal file
View File

8
sdkconfig.defaults Normal file
View File

@ -0,0 +1,8 @@
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
CONFIG_ESPTOOLPY_FLASHSIZE="8MB"
CONFIG_ESP_MAXIMUM_RETRY=3