Merge branch 'master' into hex_v302

This commit is contained in:
Ben 2024-02-25 12:38:39 -05:00
commit e561f60220
76 changed files with 11875 additions and 3495 deletions

View File

@ -24,4 +24,4 @@
],
"idf.port": "/dev/cu.usbmodem1434301",
"C_Cpp.intelliSenseEngine": "Tag Parser"
}
}

View File

@ -1,5 +1,6 @@
idf_component_register(
SRCS
"bm1368.c"
"bm1366.c"
"bm1397.c"
"serial.c"

View File

@ -179,7 +179,7 @@ void BM1366_send_hash_frequency(float target_freq)
}
}
_send_BM1366((TYPE_CMD | GROUP_ALL | CMD_WRITE), freqbuf, 6, true);
_send_BM1366((TYPE_CMD | GROUP_ALL | CMD_WRITE), freqbuf, 6, false);
ESP_LOGI(TAG, "Setting Frequency to %.2fMHz (%.2f)", target_freq, newf);
}
@ -665,7 +665,7 @@ asic_result * BM1366_receive_work(void)
return SERIAL_rx_aa55(asic_response_buffer, 11);
}
uint16_t reverse_uint16(uint16_t num)
static uint16_t reverse_uint16(uint16_t num)
{
return (num >> 8) | (num << 8);
}

537
components/bm1397/bm1368.c Normal file
View File

@ -0,0 +1,537 @@
#include "bm1368.h"
#include "crc.h"
#include "global_state.h"
#include "serial.h"
#include "utils.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BM1368_RST_PIN GPIO_NUM_1
#define TYPE_JOB 0x20
#define TYPE_CMD 0x40
#define GROUP_SINGLE 0x00
#define GROUP_ALL 0x10
#define CMD_JOB 0x01
#define CMD_SETADDRESS 0x00
#define CMD_WRITE 0x01
#define CMD_READ 0x02
#define CMD_INACTIVE 0x03
#define RESPONSE_CMD 0x00
#define RESPONSE_JOB 0x80
#define SLEEP_TIME 20
#define FREQ_MULT 25.0
#define CLOCK_ORDER_CONTROL_0 0x80
#define CLOCK_ORDER_CONTROL_1 0x84
#define ORDERED_CLOCK_ENABLE 0x20
#define CORE_REGISTER_CONTROL 0x3C
#define PLL3_PARAMETER 0x68
#define FAST_UART_CONFIGURATION 0x28
#define TICKET_MASK 0x14
#define MISC_CONTROL 0x18
typedef struct __attribute__((__packed__))
{
uint8_t preamble[2];
uint32_t nonce;
uint8_t midstate_num;
uint8_t job_id;
uint16_t version;
uint8_t crc;
} asic_result;
static const char * TAG = "bm1368Module";
static uint8_t asic_response_buffer[CHUNK_SIZE];
static task_result result;
/// @brief
/// @param ftdi
/// @param header
/// @param data
/// @param len
static void _send_BM1368(uint8_t header, uint8_t * data, uint8_t data_len, bool debug)
{
packet_type_t packet_type = (header & TYPE_JOB) ? JOB_PACKET : CMD_PACKET;
uint8_t total_length = (packet_type == JOB_PACKET) ? (data_len + 6) : (data_len + 5);
// allocate memory for buffer
unsigned char * buf = malloc(total_length);
// add the preamble
buf[0] = 0x55;
buf[1] = 0xAA;
// add the header field
buf[2] = header;
// add the length field
buf[3] = (packet_type == JOB_PACKET) ? (data_len + 4) : (data_len + 3);
// add the data
memcpy(buf + 4, data, data_len);
// add the correct crc type
if (packet_type == JOB_PACKET) {
uint16_t crc16_total = crc16_false(buf + 2, data_len + 2);
buf[4 + data_len] = (crc16_total >> 8) & 0xFF;
buf[5 + data_len] = crc16_total & 0xFF;
} else {
buf[4 + data_len] = crc5(buf + 2, data_len + 2);
}
// send serial data
SERIAL_send(buf, total_length, debug);
free(buf);
}
static void _send_simple(uint8_t * data, uint8_t total_length)
{
unsigned char * buf = malloc(total_length);
memcpy(buf, data, total_length);
SERIAL_send(buf, total_length, false);
free(buf);
}
static void _send_chain_inactive(void)
{
unsigned char read_address[2] = {0x00, 0x00};
// send serial data
_send_BM1368((TYPE_CMD | GROUP_ALL | CMD_INACTIVE), read_address, 2, false);
}
static void _set_chip_address(uint8_t chipAddr)
{
unsigned char read_address[2] = {chipAddr, 0x00};
// send serial data
_send_BM1368((TYPE_CMD | GROUP_SINGLE | CMD_SETADDRESS), read_address, 2, false);
}
void BM1368_send_hash_frequency(float target_freq)
{
// default 200Mhz if it fails
unsigned char freqbuf[9] = {0x00, 0x08, 0x40, 0xA0, 0x02, 0x41}; // freqbuf - pll0_parameter
float newf = 200.0;
uint8_t fb_divider = 0;
uint8_t post_divider1 = 0, post_divider2 = 0;
uint8_t ref_divider = 0;
float min_difference = 10;
// refdiver is 2 or 1
// postdivider 2 is 1 to 7
// postdivider 1 is 1 to 7 and less than postdivider 2
// fbdiv is 144 to 235
for (uint8_t refdiv_loop = 2; refdiv_loop > 0 && fb_divider == 0; refdiv_loop--) {
for (uint8_t postdiv1_loop = 7; postdiv1_loop > 0 && fb_divider == 0; postdiv1_loop--) {
for (uint8_t postdiv2_loop = 1; postdiv2_loop < postdiv1_loop && fb_divider == 0; postdiv2_loop++) {
int temp_fb_divider = round(((float) (postdiv1_loop * postdiv2_loop * target_freq * refdiv_loop) / 25.0));
if (temp_fb_divider >= 144 && temp_fb_divider <= 235) {
float temp_freq = 25.0 * (float) temp_fb_divider / (float) (refdiv_loop * postdiv2_loop * postdiv1_loop);
float freq_diff = fabs(target_freq - temp_freq);
if (freq_diff < min_difference) {
fb_divider = temp_fb_divider;
post_divider1 = postdiv1_loop;
post_divider2 = postdiv2_loop;
ref_divider = refdiv_loop;
min_difference = freq_diff;
break;
}
}
}
}
}
if (fb_divider == 0) {
puts("Finding dividers failed, using default value (200Mhz)");
} else {
newf = 25.0 / (float) (ref_divider * fb_divider) / (float) (post_divider1 * post_divider2);
printf("final refdiv: %d, fbdiv: %d, postdiv1: %d, postdiv2: %d, min diff value: %f\n", ref_divider, fb_divider,
post_divider1, post_divider2, min_difference);
freqbuf[3] = fb_divider;
freqbuf[4] = ref_divider;
freqbuf[5] = (((post_divider1 - 1) & 0xf) << 4) + ((post_divider2 - 1) & 0xf);
if (fb_divider * 25 / (float) ref_divider >= 2400) {
freqbuf[2] = 0x50;
}
}
_send_BM1368((TYPE_CMD | GROUP_ALL | CMD_WRITE), freqbuf, 6, false);
ESP_LOGI(TAG, "Setting Frequency to %.2fMHz (%.2f)", target_freq, newf);
}
static void do_frequency_ramp_up() {
//PLLO settings taken from a S21 dump.
//todo: do this right.
uint8_t freq_list[65][4] = {{0x40, 0xA2, 0x02, 0x55},
{0x40, 0xAF, 0x02, 0x64},
{0x40, 0xA5, 0x02, 0x54},
{0x40, 0xA8, 0x02, 0x63},
{0x40, 0xB6, 0x02, 0x63},
{0x40, 0xA8, 0x02, 0x53},
{0x40, 0xB4, 0x02, 0x53},
{0x40, 0xA8, 0x02, 0x62},
{0x40, 0xAA, 0x02, 0x43},
{0x40, 0xA2, 0x02, 0x52},
{0x40, 0xAB, 0x02, 0x52},
{0x40, 0xB4, 0x02, 0x52},
{0x40, 0xBD, 0x02, 0x52},
{0x40, 0xA5, 0x02, 0x42},
{0x40, 0xA1, 0x02, 0x61},
{0x40, 0xA8, 0x02, 0x61},
{0x40, 0xAF, 0x02, 0x61},
{0x40, 0xB6, 0x02, 0x61},
{0x40, 0xA2, 0x02, 0x51},
{0x40, 0xA8, 0x02, 0x51},
{0x40, 0xAE, 0x02, 0x51},
{0x40, 0xB4, 0x02, 0x51},
{0x40, 0xBA, 0x02, 0x51},
{0x40, 0xA0, 0x02, 0x41},
{0x40, 0xA5, 0x02, 0x41},
{0x40, 0xAA, 0x02, 0x41},
{0x40, 0xAF, 0x02, 0x41},
{0x40, 0xB4, 0x02, 0x41},
{0x40, 0xB9, 0x02, 0x41},
{0x40, 0xBE, 0x02, 0x41},
{0x40, 0xA0, 0x02, 0x31},
{0x40, 0xA4, 0x02, 0x31},
{0x40, 0xA8, 0x02, 0x31},
{0x40, 0xAC, 0x02, 0x31},
{0x40, 0xB0, 0x02, 0x31},
{0x40, 0xB4, 0x02, 0x31},
{0x40, 0xA1, 0x02, 0x60},
{0x40, 0xBC, 0x02, 0x31},
{0x40, 0xA8, 0x02, 0x60},
{0x40, 0xAF, 0x02, 0x60},
{0x50, 0xCC, 0x02, 0x31},
{0x40, 0xB6, 0x02, 0x60},
{0x50, 0xD4, 0x02, 0x31},
{0x40, 0xA2, 0x02, 0x50},
{0x40, 0xA5, 0x02, 0x50},
{0x40, 0xA8, 0x02, 0x50},
{0x40, 0xAB, 0x02, 0x50},
{0x40, 0xAE, 0x02, 0x50},
{0x40, 0xB1, 0x02, 0x50},
{0x40, 0xB4, 0x02, 0x50},
{0x40, 0xB7, 0x02, 0x50},
{0x40, 0xBA, 0x02, 0x50},
{0x40, 0xBD, 0x02, 0x50},
{0x40, 0xA0, 0x02, 0x40},
{0x50, 0xC3, 0x02, 0x50},
{0x40, 0xA5, 0x02, 0x40},
{0x50, 0xC9, 0x02, 0x50},
{0x40, 0xAA, 0x02, 0x40},
{0x50, 0xCF, 0x02, 0x50},
{0x40, 0xAF, 0x02, 0x40},
{0x50, 0xD5, 0x02, 0x50},
{0x40, 0xB4, 0x02, 0x40},
{0x50, 0xDB, 0x02, 0x50},
{0x40, 0xB9, 0x02, 0x40},
{0x50, 0xE0, 0x02, 0x50}};
uint8_t freq_cmd[6] = {0x00, 0x08, 0x40, 0xB4, 0x02, 0x40};
for (int i = 0; i < 65; i++) {
freq_cmd[2] = freq_list[i][0];
freq_cmd[3] = freq_list[i][1];
freq_cmd[4] = freq_list[i][2];
freq_cmd[5] = freq_list[i][3];
_send_BM1368((TYPE_CMD | GROUP_ALL | CMD_WRITE), freq_cmd, 6, false);
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
static void _send_init(uint64_t frequency) {
//enable and set version rolling mask to 0xFFFF
unsigned char init0[11] = {0x55, 0xAA, 0x51, 0x09, 0x00, 0xA4, 0x90, 0x00, 0xFF, 0xFF, 0x1C};
_send_simple(init0, 11);
//enable and set version rolling mask to 0xFFFF (again)
unsigned char init1[11] = {0x55, 0xAA, 0x51, 0x09, 0x00, 0xA4, 0x90, 0x00, 0xFF, 0xFF, 0x1C};
_send_simple(init1, 11);
//enable and set version rolling mask to 0xFFFF (again)
unsigned char init2[11] = {0x55, 0xAA, 0x51, 0x09, 0x00, 0xA4, 0x90, 0x00, 0xFF, 0xFF, 0x1C};
_send_simple(init2, 11);
//read register 00 on all chips (should respond AA 55 13 68 00 00 00 00 00 00 0F)
unsigned char init3[7] = {0x55, 0xAA, 0x52, 0x05, 0x00, 0x00, 0x0A};
_send_simple(init3, 7);
//enable and set version rolling mask to 0xFFFF (again)
unsigned char init4[11] = {0x55, 0xAA, 0x51, 0x09, 0x00, 0xA4, 0x90, 0x00, 0xFF, 0xFF, 0x1C};
_send_simple(init4, 11);
//Reg_A8
unsigned char init5[11] = {0x55, 0xAA, 0x51, 0x09, 0x00, 0xA8, 0x00, 0x07, 0x00, 0x00, 0x03};
_send_simple(init5, 11);
//Misc Control
unsigned char init6[11] = {0x55, 0xAA, 0x51, 0x09, 0x00, 0x18, 0xFF, 0x0F, 0xC1, 0x00, 0x00};
_send_simple(init6, 11);
//chain inactive
unsigned char init7[7] = {0x55, 0xAA, 0x53, 0x05, 0x00, 0x00, 0x03};
_send_simple(init7, 7);
//assign address 0x00 to the first chip
unsigned char init8[7] = {0x55, 0xAA, 0x40, 0x05, 0x00, 0x00, 0x1C};
_send_simple(init8, 7);
//Core Register Control
unsigned char init9[11] = {0x55, 0xAA, 0x51, 0x09, 0x00, 0x3C, 0x80, 0x00, 0x8B, 0x00, 0x12};
_send_simple(init9, 11);
//Core Register Control
unsigned char init10[11] = {0x55, 0xAA, 0x51, 0x09, 0x00, 0x3C, 0x80, 0x00, 0x80, 0x18, 0x1F};
_send_simple(init10, 11);
//set ticket mask
unsigned char init11[11] = {0x55, 0xAA, 0x51, 0x09, 0x00, 0x14, 0x00, 0x00, 0x00, 0xFF, 0x08};
_send_simple(init11, 11);
//Analog Mux Control
unsigned char init12[11] = {0x55, 0xAA, 0x51, 0x09, 0x00, 0x54, 0x00, 0x00, 0x00, 0x03, 0x1D};
_send_simple(init12, 11);
//Set the IO Driver Strength on chip 00
unsigned char init13[11] = {0x55, 0xAA, 0x51, 0x09, 0x00, 0x58, 0x02, 0x11, 0x11, 0x11, 0x06};
_send_simple(init13, 11);
//Reg_A8
unsigned char init14[11] = {0x55, 0xAA, 0x41, 0x09, 0x00, 0xA8, 0x00, 0x07, 0x01, 0xF0, 0x15};
_send_simple(init14, 11);
//Misc Control
unsigned char init15[11] = {0x55, 0xAA, 0x41, 0x09, 0x00, 0x18, 0xF0, 0x00, 0xC1, 0x00, 0x0C};
_send_simple(init15, 11);
//Core Register Control
unsigned char init16[11] = {0x55, 0xAA, 0x41, 0x09, 0x00, 0x3C, 0x80, 0x00, 0x8B, 0x00, 0x1A};
_send_simple(init16, 11);
//Core Register Control
unsigned char init17[11] = {0x55, 0xAA, 0x41, 0x09, 0x00, 0x3C, 0x80, 0x00, 0x80, 0x18, 0x17};
_send_simple(init17, 11);
//Core Register Control
unsigned char init18[11] = {0x55, 0xAA, 0x41, 0x09, 0x00, 0x3C, 0x80, 0x00, 0x82, 0xAA, 0x05};
_send_simple(init18, 11);
do_frequency_ramp_up();
BM1368_send_hash_frequency(frequency);
}
// reset the BM1368 via the RTS line
static void _reset(void)
{
gpio_set_level(BM1368_RST_PIN, 0);
// delay for 100ms
vTaskDelay(100 / portTICK_PERIOD_MS);
// set the gpio pin high
gpio_set_level(BM1368_RST_PIN, 1);
// delay for 100ms
vTaskDelay(100 / portTICK_PERIOD_MS);
}
static void _send_read_address(void)
{
unsigned char read_address[2] = {0x00, 0x00};
// send serial data
_send_BM1368((TYPE_CMD | GROUP_ALL | CMD_READ), read_address, 2, false);
}
void BM1368_init(uint64_t frequency)
{
ESP_LOGI(TAG, "Initializing BM1368");
memset(asic_response_buffer, 0, 1024);
esp_rom_gpio_pad_select_gpio(BM1368_RST_PIN);
gpio_set_direction(BM1368_RST_PIN, GPIO_MODE_OUTPUT);
// reset the bm1368
_reset();
// send the init command
//_send_read_address();
_send_init(frequency);
}
// Baud formula = 25M/((denominator+1)*8)
// The denominator is 5 bits found in the misc_control (bits 9-13)
int BM1368_set_default_baud(void)
{
// default divider of 26 (11010) for 115,749
unsigned char baudrate[9] = {0x00, MISC_CONTROL, 0x00, 0x00, 0b01111010, 0b00110001}; // baudrate - misc_control
_send_BM1368((TYPE_CMD | GROUP_ALL | CMD_WRITE), baudrate, 6, false);
return 115749;
}
int BM1368_set_max_baud(void)
{
/// return 115749;
// divider of 0 for 3,125,000
ESP_LOGI(TAG, "Setting max baud of 1000000 ");
unsigned char init8[11] = {0x55, 0xAA, 0x51, 0x09, 0x00, 0x28, 0x11, 0x30, 0x02, 0x00, 0x03};
_send_simple(init8, 11);
return 1000000;
}
void BM1368_set_job_difficulty_mask(int difficulty)
{
return;
// Default mask of 256 diff
unsigned char job_difficulty_mask[9] = {0x00, TICKET_MASK, 0b00000000, 0b00000000, 0b00000000, 0b11111111};
// The mask must be a power of 2 so there are no holes
// Correct: {0b00000000, 0b00000000, 0b11111111, 0b11111111}
// Incorrect: {0b00000000, 0b00000000, 0b11100111, 0b11111111}
// (difficulty - 1) if it is a pow 2 then step down to second largest for more hashrate sampling
difficulty = _largest_power_of_two(difficulty) - 1;
// convert difficulty into char array
// Ex: 256 = {0b00000000, 0b00000000, 0b00000000, 0b11111111}, {0x00, 0x00, 0x00, 0xff}
// Ex: 512 = {0b00000000, 0b00000000, 0b00000001, 0b11111111}, {0x00, 0x00, 0x01, 0xff}
for (int i = 0; i < 4; i++) {
char value = (difficulty >> (8 * i)) & 0xFF;
// The char is read in backwards to the register so we need to reverse them
// So a mask of 512 looks like 0b00000000 00000000 00000001 1111111
// and not 0b00000000 00000000 10000000 1111111
job_difficulty_mask[5 - i] = _reverse_bits(value);
}
ESP_LOGI(TAG, "Setting job ASIC mask to %d", difficulty);
_send_BM1368((TYPE_CMD | GROUP_ALL | CMD_WRITE), job_difficulty_mask, 6, false);
}
static uint8_t id = 0;
void BM1368_send_work(void * pvParameters, bm_job * next_bm_job)
{
GlobalState * GLOBAL_STATE = (GlobalState *) pvParameters;
BM1368_job job;
id = (id + 24) % 128;
job.job_id = id;
job.num_midstates = 0x01;
memcpy(&job.starting_nonce, &next_bm_job->starting_nonce, 4);
memcpy(&job.nbits, &next_bm_job->target, 4);
memcpy(&job.ntime, &next_bm_job->ntime, 4);
memcpy(job.merkle_root, next_bm_job->merkle_root_be, 32);
memcpy(job.prev_block_hash, next_bm_job->prev_block_hash_be, 32);
memcpy(&job.version, &next_bm_job->version, 4);
if (GLOBAL_STATE->ASIC_TASK_MODULE.active_jobs[job.job_id] != NULL) {
free_bm_job(GLOBAL_STATE->ASIC_TASK_MODULE.active_jobs[job.job_id]);
}
GLOBAL_STATE->ASIC_TASK_MODULE.active_jobs[job.job_id] = next_bm_job;
pthread_mutex_lock(&GLOBAL_STATE->valid_jobs_lock);
GLOBAL_STATE->valid_jobs[job.job_id] = 1;
// ESP_LOGI(TAG, "Added Job: %i", job.job_id);
pthread_mutex_unlock(&GLOBAL_STATE->valid_jobs_lock);
_send_BM1368((TYPE_JOB | GROUP_SINGLE | CMD_WRITE), &job, sizeof(BM1368_job), false);
}
asic_result * BM1368_receive_work(void)
{
// wait for a response, wait time is pretty arbitrary
int received = SERIAL_rx(asic_response_buffer, 11, 60000);
if (received < 0) {
ESP_LOGI(TAG, "Error in serial RX");
return NULL;
} else if (received == 0) {
// Didn't find a solution, restart and try again
return NULL;
}
if (received != 11 || asic_response_buffer[0] != 0xAA || asic_response_buffer[1] != 0x55) {
ESP_LOGI(TAG, "Serial RX invalid %i", received);
ESP_LOG_BUFFER_HEX(TAG, asic_response_buffer, received);
return NULL;
}
return (asic_result *) asic_response_buffer;
}
static uint16_t reverse_uint16(uint16_t num)
{
return (num >> 8) | (num << 8);
}
task_result * BM1368_proccess_work(void * pvParameters)
{
asic_result * asic_result = BM1368_receive_work();
if (asic_result == NULL) {
return NULL;
}
uint8_t job_id = asic_result->job_id;
uint8_t rx_job_id = ((int8_t)job_id & 0xf0) >> 1;
ESP_LOGI(TAG, "RX Job ID: %02X", rx_job_id);
GlobalState * GLOBAL_STATE = (GlobalState *) pvParameters;
if (GLOBAL_STATE->valid_jobs[rx_job_id] == 0) {
ESP_LOGE(TAG, "Invalid job nonce found, 0x%02X", rx_job_id);
return NULL;
}
uint32_t rolled_version = GLOBAL_STATE->ASIC_TASK_MODULE.active_jobs[rx_job_id]->version;
// // // shift the 16 bit value left 13
rolled_version = (reverse_uint16(asic_result->version) << 13) | rolled_version;
result.job_id = rx_job_id;
result.nonce = asic_result->nonce;
result.rolled_version = rolled_version;
return &result;
}

View File

@ -0,0 +1,44 @@
#ifndef BM1368_H_
#define BM1368_H_
#include "common.h"
#include "driver/gpio.h"
#include "mining.h"
#define CRC5_MASK 0x1F
// static const u_int64_t BM1368_FREQUENCY = CONFIG_ASIC_FREQUENCY;
static const u_int64_t BM1368_CORE_COUNT = 672;
// static const u_int64_t BM1368_HASHRATE_S = BM1368_FREQUENCY * BM1368_CORE_COUNT * 1000000;
// 2^32
// static const u_int64_t NONCE_SPACE = 4294967296;
static const double BM1368_FULLSCAN_MS = 2140;
typedef struct
{
float frequency;
} bm1368Module;
typedef struct __attribute__((__packed__))
{
uint8_t job_id;
uint8_t num_midstates;
uint8_t starting_nonce[4];
uint8_t nbits[4];
uint8_t ntime[4];
uint8_t merkle_root[32];
uint8_t prev_block_hash[32];
uint8_t version[4];
} BM1368_job;
void BM1368_init(u_int64_t frequency);
void BM1368_send_init(void);
void BM1368_send_work(void * GLOBAL_STATE, bm_job * next_bm_job);
void BM1368_set_job_difficulty_mask(int);
int BM1368_set_max_baud(void);
int BM1368_set_default_baud(void);
void BM1368_send_hash_frequency(float frequency);
task_result * BM1368_proccess_work(void * GLOBAL_STATE);
#endif /* BM1368_H_ */

View File

@ -122,16 +122,16 @@ void STRATUM_V1_parse(StratumApiV1Message * message, const char * stratum_json)
} else {
// parse results
cJSON * result_json = cJSON_GetObjectItem(json, "result");
if (result_json != NULL && cJSON_IsBool(result_json)) {
if (result_json == NULL){
message->response_success = false;
}
else if (cJSON_IsBool(result_json)) {
result = STRATUM_RESULT;
bool response_success = false;
if (result_json != NULL && cJSON_IsTrue(result_json)) {
response_success = true;
if (cJSON_IsTrue(result_json)) {
message->response_success = true;
}else{
message->response_success = false;
}
message->response_success = response_success;
} else {
cJSON * mask = cJSON_GetObjectItem(result_json, "version-rolling.mask");
if (mask != NULL) {

View File

@ -2,6 +2,7 @@
#define GLOBAL_STATE_H_
#include "asic_task.h"
#include "bm1368.h"
#include "bm1366.h"
#include "bm1397.h"
#include "common.h"

View File

@ -46,8 +46,8 @@
"budgets": [
{
"type": "initial",
"maximumWarning": "750kb",
"maximumError": "1mb"
"maximumWarning": "2mb",
"maximumError": "3mb"
},
{
"type": "anyComponentStyle",

View File

@ -0,0 +1,16 @@
const fs = require('fs');
const path = require('path');
const directory = './dist/axe-os';
fs.readdir(directory, (err, files) => {
if (err) throw err;
for (const file of files) {
if (!file.endsWith('.gz')) {
fs.unlink(path.join(directory, file), err => {
if (err) throw err;
});
}
}
});

File diff suppressed because it is too large Load Diff

View File

@ -4,31 +4,37 @@
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build --configuration=production && gzipper compress --verbose --gzip --gzip-level 9 ./dist/axe-os",
"build": "ng build --configuration=production && gzipper compress --verbose --gzip --gzip-level 9 ./dist/axe-os && node only-gzip.js",
"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",
"@angular/animations": "17.2.2",
"@angular/common": "17.2.2",
"@angular/compiler": "17.2.2",
"@angular/core": "17.2.2",
"@angular/forms": "17.2.2",
"@angular/platform-browser": "17.2.2",
"@angular/platform-browser-dynamic": "17.2.2",
"@angular/router": "17.2.2",
"chart.js": "^4.4.1",
"chartjs-adapter-moment": "^1.0.1",
"moment": "^2.30.1",
"ngx-toastr": "^17.0.2",
"primeflex": "^3.3.1",
"primeicons": "^6.0.1",
"primeng": "^17.8.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"xterm": "^5.2.1",
"zone.js": "~0.13.0"
"zone.js": "~0.14.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^16.1.3",
"@angular/cli": "~16.1.3",
"@angular/compiler-cli": "^16.1.0",
"@angular-devkit/build-angular": "17.2.1",
"@angular/cli": "17.2.1",
"@angular/compiler-cli": "17.2.2",
"@types/jasmine": "~4.3.0",
"gzipper": "^7.2.0",
"jasmine-core": "~4.6.0",
@ -37,7 +43,7 @@
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.1.3",
"typescript": "~5.3.3",
"webpack-bundle-analyzer": "^4.9.0"
}
}

View File

@ -2,22 +2,35 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './components/home/home.component';
import { LogsComponent } from './components/logs/logs.component';
import { SettingsComponent } from './components/settings/settings.component';
import { SwarmComponent } from './components/swarm/swarm.component';
import { AppLayoutComponent } from './layout/app.layout.component';
const routes: Routes = [
{
path: '',
component: HomeComponent
component: AppLayoutComponent,
children: [
{
path: '',
component: HomeComponent
},
{
path: 'logs',
component: LogsComponent
},
{
path: 'settings',
component: SettingsComponent
},
{
path: 'swarm',
component: SwarmComponent
}
]
},
{
path: 'settings',
component: SettingsComponent
},
{
path: 'swarm',
component: SwarmComponent
}
];
@NgModule({

View File

@ -1,22 +1,9 @@
<app-loading></app-loading>
<app-header></app-header>
<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]="['settings']" 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>

View File

@ -1,69 +0,0 @@
.body {
margin-top: 100px;
padding-left: 5vw;
padding-right: 5vw;
}
@media only screen and (max-width:900px) {
.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: 50px;
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;
}
}

View File

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

View File

@ -1,3 +1,5 @@
import 'chartjs-adapter-moment';
import { CommonModule, HashLocationStrategy, LocationStrategy } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
@ -9,21 +11,26 @@ 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';
import { LogsComponent } from './components/logs/logs.component';
import { SettingsComponent } from './components/settings/settings.component';
import { SwarmComponent } from './components/swarm/swarm.component';
import { AppLayoutModule } from './layout/app.layout.module';
import { ANSIPipe } from './pipes/ansi.pipe';
import { DateAgoPipe } from './pipes/date-ago.pipe';
import { HashSuffixPipe } from './pipes/hash-suffix.pipe';
import { PrimeNGModule } from './prime-ng.module';
const components = [
AppComponent,
HeaderComponent,
EditComponent,
HomeComponent,
LoadingComponent,
SettingsComponent
SettingsComponent,
LogsComponent
];
@NgModule({
@ -33,7 +40,8 @@ const components = [
ANSIPipe,
DateAgoPipe,
SwarmComponent,
SettingsComponent
SettingsComponent,
HashSuffixPipe
],
imports: [
BrowserModule,
@ -45,7 +53,9 @@ const components = [
positionClass: 'toast-bottom-right'
}),
BrowserAnimationsModule,
CommonModule
CommonModule,
PrimeNGModule,
AppLayoutModule
],
providers: [
{ provide: LocationStrategy, useClass: HashLocationStrategy },

View File

@ -1,129 +1,160 @@
<ng-container *ngIf="form != null">
<form [formGroup]="form">
<div class="form-group">
<label>Flip Screen</label>
<input formControlName="flipscreen" type="checkbox">
<div class="field grid p-fluid">
<label htmlFor="ssid" class="col-12 mb-2 md:col-2 md:mb-0">WiFi SSID:</label>
<div class="col-12 md:col-10">
<input pInputText id="ssid" type="text" formControlName="ssid" />
</div>
</div>
<!-- <div class="form-group">
<label>Invert Screen</label>
<input formControlName="invertscreen" type="checkbox">
</div> -->
<div class="form-group">
<label>WiFi SSID: </label>
<input formControlName="ssid" type="text">
<div class="field grid p-fluid">
<label htmlFor="wifiPass" class="col-12 mb-2 md:col-2 md:mb-0">WiFi Password:</label>
<div class="col-12 md:col-10">
<input pInputText id="wifiPass" formControlName="wifiPass" type="password" />
</div>
</div>
<div class="form-group">
<label>WiFi Password: </label>
<input formControlName="wifiPass" type="password">
<div class="field grid p-fluid">
<label htmlFor="stratumURL" class="col-12 mb-2 md:col-2 md:mb-0">Stratum URL:</label>
<div class="col-12 md:col-10">
<input pInputText id="stratumURL" type="text" formControlName="stratumURL"
formControlName="stratumURL" />
<div>
<small>Do not include 'stratum+tcp://' or port.</small>
</div>
</div>
</div>
<div class="form-group">
<label>Stratum URL: (Do not include 'stratum+tcp://' or port</label>
<input formControlName="stratumURL" type="text">
<div class="field grid p-fluid">
<label htmlFor="stratumPort" class="col-12 mb-2 md:col-2 md:mb-0">Stratum Port:</label>
<div class="col-12 md:col-10">
<input pInputText id="stratumPort" formControlName="stratumPort" type="number" />
</div>
</div>
<div class="form-group">
<label>Stratum Port:</label>
<input formControlName="stratumPort" type="number">
<div class="field grid p-fluid">
<label htmlFor="stratumUser" class="col-12 mb-2 md:col-2 md:mb-0">Stratum User:</label>
<div class="col-12 md:col-10">
<input pInputText id="stratumUser" formControlName="stratumUser" type="text" />
</div>
</div>
<div class="form-group">
<label>Stratum User: </label>
<input formControlName="stratumUser" type="text">
</div>
<div class="form-group">
<label>Stratum Password: </label>
<input formControlName="stratumPassword" type="password">
<div class="field grid p-fluid">
<label htmlFor="stratumPassword" class="col-12 mb-2 md:col-2 md:mb-0">Stratum Password:</label>
<div class="col-12 md:col-10">
<input pInputText id="stratumPassword" formControlName="stratumPassword" type="password" />
</div>
</div>
<ng-container *ngIf="!devToolsOpen && ASICModel == eASICModel.BM1366">
<div class="form-group">
<label>Frequency </label>
<select formControlName="frequency">
<option value="400">400</option>
<option value="425">425</option>
<option value="450">450</option>
<option value="475">475</option>
<option value="485">485 (default)</option>
<option value="500">500</option>
<option value="525">525</option>
<option value="550">550</option>
<option value="575">575</option>
</select>
<div class="field grid p-fluid">
<label class="col-12 mb-2 md:col-2 md:mb-0" htmlFor="frequency">Frequency</label>
<div class="col-12 md:col-10">
<p-dropdown [options]="BM1366DropdownFrequency" optionLabel="name" optionValue="value"
formControlName="frequency"></p-dropdown>
</div>
</div>
<div class="form-group">
<label>Core Voltage </label>
<select formControlName="coreVoltage">
<option value="1100">1100</option>
<option value="1150">1150</option>
<option value="1200">1200 (default)</option>
<option value="1250">1250</option>
<option value="1300">1300</option>
</select>
<div class="field grid p-fluid">
<label class="col-12 mb-2 md:col-2 md:mb-0" htmlFor="coreVoltage">Core Voltage</label>
<p-dropdown class="col-12 md:col-10" [options]="BM1366CoreVoltage" optionLabel="name"
optionValue="value" formControlName="coreVoltage"></p-dropdown>
</div>
</ng-container>
<ng-container *ngIf="!devToolsOpen && ASICModel == eASICModel.BM1368">
<div class="field grid p-fluid">
<label class="col-12 mb-2 md:col-2 md:mb-0" htmlFor="frequency">Frequency</label>
<div class="col-12 md:col-10">
<p-dropdown [options]="BM1368DropdownFrequency" optionLabel="name" optionValue="value"
formControlName="frequency"></p-dropdown>
</div>
</div>
<div class="field grid p-fluid">
<label class="col-12 mb-2 md:col-2 md:mb-0" htmlFor="coreVoltage">Core Voltage</label>
<p-dropdown class="col-12 md:col-10" [options]="BM1368CoreVoltage" optionLabel="name"
optionValue="value" formControlName="coreVoltage"></p-dropdown>
</div>
</ng-container>
<ng-container *ngIf="!devToolsOpen && ASICModel == eASICModel.BM1397">
<div class="form-group">
<label>Frequency </label>
<select formControlName="frequency">
<option value="400">400</option>
<option value="425">425 (default)</option>
<option value="450">450</option>
<option value="475">475</option>
<option value="485">485</option>
<option value="500">500</option>
<option value="525">525</option>
<option value="550">550</option>
<option value="575">575</option>
</select>
<div class="field grid p-fluid">
<label class="col-12 mb-2 md:col-2 md:mb-0" htmlFor="frequency">Frequency</label>
<p-dropdown class="col-12 md:col-10" [options]="BM1397DropdownFrequency" optionLabel="name"
optionValue="value" formControlName="frequency"></p-dropdown>
</div>
<div class="form-group">
<label>Core Voltage </label>
<select formControlName="coreVoltage">
<option value="1300">1300</option>
<option value="1350">1350</option>
<option value="1400">1400 (default)</option>
<option value="1450">1450</option>
<option value="1500">1500</option>
</select>
<div class="field grid p-fluid">
<label class="col-12 mb-2 md:col-2 md:mb-0" htmlFor="coreVoltage">Core Voltage</label>
<p-dropdown class="col-12 md:col-10" [options]="BM1397CoreVoltage" optionLabel="name"
optionValue="value" formControlName="coreVoltage"></p-dropdown>
</div>
</ng-container>
<ng-container *ngIf="devToolsOpen === true">
<div class="form-group">
<label>Frequency </label>
<input formControlName="frequency" type="number">
<div class="field grid p-fluid">
<label htmlFor="frequency" class="col-12 mb-2 md:col-2 md:mb-0">Frequency</label>
<div class="col-12 md:col-10">
<input pInputText id="frequency" formControlName="frequency" type="number" />
</div>
</div>
<div class="form-group">
<label>Core Voltage</label>
<input formControlName="coreVoltage" type="number">
<div class="field grid p-fluid">
<label htmlFor="coreVoltage" class="col-12 mb-2 md:col-2 md:mb-0">Core Voltage</label>
<div class="col-12 md:col-10">
<input pInputText id="coreVoltage" formControlName="coreVoltage" type="number" />
</div>
</div>
</ng-container>
<div class="form-group">
<label>Invert Fan Polarity</label>
<input formControlName="invertfanpolarity" type="checkbox">
<div class="col-12 md:col-4">
<div class="field-checkbox">
<p-checkbox name="flipscreen" formControlName="flipscreen" inputId="flipscreen"
[binary]="true"></p-checkbox>
<label for="flipscreen">Flip Screen</label>
</div>
</div>
<div class="form-group">
<label>Automatic Fan Control</label>
<input formControlName="autofanspeed" type="checkbox">
<div class="col-12 md:col-4">
<div class="field-checkbox">
<p-checkbox name="invertfanpolarity" formControlName="invertfanpolarity" inputId="invertfanpolarity"
[binary]="true"></p-checkbox>
<label for="invertfanpolarity">Invert Fan Polarity</label>
</div>
</div>
<div class="col-12 md:col-4">
<div class="field-checkbox">
<p-checkbox name="autofanspeed" formControlName="autofanspeed" inputId="autofanspeed"
[binary]="true"></p-checkbox>
<label for="autofanspeed">Automatic Fan Control</label>
</div>
</div>
<div class="form-group" *ngIf="form.controls['autofanspeed'].value != true">
<label>Fan Speed {{form.controls['fanspeed'].value}}%
<b *ngIf="form.controls['fanspeed'].value < 33" style="color:red">Danger: Could Cause
Overheating</b> <b *ngIf="form.controls['fanspeed'].value == 100" style="color: #F2A900">S19
Simulator</b></label>
<input formControlName="fanspeed" type="range" [min]="0" [max]="100">
<div *ngIf="form.controls['autofanspeed'].value != true">
<div class="col-12" *ngIf="form.controls['autofanspeed'].value != true">
<label>Fan Speed {{form.controls['fanspeed'].value}}%
<b *ngIf="form.controls['fanspeed'].value < 33" style="color:red">Danger: Could Cause
Overheating</b> <b *ngIf="form.controls['fanspeed'].value == 100" style="color: #F2A900">S19
Simulator</b></label>
<p-slider formControlName="fanspeed"></p-slider>
</div>
</div>
<div class="mt-2">
<button [disabled]="form.invalid" (click)="updateSystem()" class="btn btn-primary mr-2">Save</button>
<button pButton [disabled]="form.invalid" (click)="updateSystem()"
class="btn btn-primary mr-2">Save</button>
<b style="line-height: 34px;">You must restart this device after saving for changes to take effect.</b>
</div>

View File

@ -1,31 +0,0 @@
input[type="text"],
input[type="number"],
input[type="password"],
input[type="range"] {
min-width: 250px;
max-width: 90%;
}
select {
min-width: 268px;
max-width: 90%;
}
.restart {
margin-bottom: 1.5rem;
}
@media only screen and (min-width:900px) {
input[type="text"],
input[type="password"],
input[type="number"],
input[type="range"] {
min-width: 500px
}
select {
min-width: 518px
}
}

View File

@ -26,6 +26,64 @@ export class EditComponent implements OnInit {
@Input() uri = '';
public BM1397DropdownFrequency = [
{ name: '400', value: 400 },
{ name: '425 (default)', value: 425 },
{ name: '450', value: 450 },
{ name: '475', value: 475 },
{ name: '485', value: 485 },
{ name: '500', value: 500 },
{ name: '525', value: 525 },
{ name: '550', value: 550 },
{ name: '575', value: 575 },
];
public BM1366DropdownFrequency = [
{ name: '400', value: 400 },
{ name: '425', value: 425 },
{ name: '450', value: 450 },
{ name: '475', value: 475 },
{ name: '485 (default)', value: 485 },
{ name: '500', value: 500 },
{ name: '525', value: 525 },
{ name: '550', value: 550 },
{ name: '575', value: 575 },
];
public BM1368DropdownFrequency = [
{ name: '400', value: 400 },
{ name: '425', value: 425 },
{ name: '450', value: 450 },
{ name: '475', value: 475 },
{ name: '490 (default)', value: 490 },
{ name: '500', value: 500 },
{ name: '525', value: 525 },
{ name: '550', value: 550 },
{ name: '575', value: 575 },
];
public BM1397CoreVoltage = [
{ name: '1100', value: 1100 },
{ name: '1150', value: 1150 },
{ name: '1200', value: 1200 },
{ name: '1250', value: 1250 },
{ name: '1300', value: 1300 },
];
public BM1366CoreVoltage = [
{ name: '1100', value: 1100 },
{ name: '1150', value: 1150 },
{ name: '1200 (default)', value: 1200 },
{ name: '1250', value: 1250 },
{ name: '1300', value: 1300 },
];
public BM1368CoreVoltage = [
{ name: '1100', value: 1100 },
{ name: '1150', value: 1150 },
{ name: '1200', value: 1200 },
{ name: '1250', value: 1250 },
{ name: '1300', value: 1300 },
];
constructor(
private fb: FormBuilder,
private systemService: SystemService,
@ -96,15 +154,6 @@ export class EditComponent implements OnInit {
const form = this.form.getRawValue();
form.frequency = parseInt(form.frequency);
form.coreVoltage = parseInt(form.coreVoltage);
// bools to ints
form.flipscreen = form.flipscreen == true ? 1 : 0;
form.invertscreen = form.invertscreen == true ? 1 : 0;
form.invertfanpolarity = form.invertfanpolarity == true ? 1 : 0;
form.autofanspeed = form.autofanspeed == true ? 1 : 0;
if (form.wifiPass === 'password') {
delete form.wifiPass;
}

View File

@ -1 +0,0 @@
<div [routerLink]="['/']" class="header-text">Axe<span class="os">OS</span></div>

View File

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

View File

@ -1,21 +0,0 @@
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

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

View File

@ -3,44 +3,182 @@
</ng-template>
<ng-container>
<div class="grid" *ngIf="info$ | async as info; else loading">
<div class="col-12">
<div class="card">
<div class="grid mb-4">
<div class="col-12 lg:col-6 xl:col-3">
<div class="card mb-0">
<div class="flex justify-content-between mb-3">
<div>
<span class="block text-500 font-medium mb-3">Hash Rate</span>
<div class="text-900 font-medium text-xl">{{info.hashRate | number: '1.2-2'}}
<small>Gh/s</small>
</div>
</div>
<div class="flex align-items-center justify-content-center bg-orange-100 border-round"
[ngStyle]="{width: '2.5rem', height: '2.5rem'}">
<i class="pi pi-bolt text-orange-500 text-xl"></i>
</div>
</div>
<ng-container *ngIf="expectedHashRate$ | async as expectedHashRate">
<span class="text-green-500 font-medium">{{expectedHashRate}}
<small>Gh/s </small></span>
<span class="text-500">expected</span>
</ng-container>
</div>
</div>
<div class="col-12 lg:col-6 xl:col-3">
<div class="card mb-0">
<div class="flex justify-content-between mb-3">
<div>
<span class="block text-500 font-medium mb-3">Shares</span>
<div class="text-900 font-medium text-xl">{{info.sharesAccepted}}</div>
</div>
<div class="flex align-items-center justify-content-center bg-blue-100 border-round"
[ngStyle]="{width: '2.5rem', height: '2.5rem'}">
<i class="pi pi-send text-blue-500 text-xl"></i>
</div>
</div>
<span class="text-red-500 font-medium">{{info.sharesRejected}} </span>
<span class="text-500">rejected</span>
</div>
</div>
<div class="col-12 lg:col-6 xl:col-3">
<div class="card mb-0">
<div class="flex justify-content-between mb-3">
<div>
<span class="block text-500 font-medium mb-3">Efficiency</span>
<div class="text-900 font-medium text-xl">
<td>{{info.power / (info.hashRate/1000) | number: '1.2-2'}} <small>W/Th</small>
</td>
</div>
</div>
<div class="flex align-items-center justify-content-center bg-orange-100 border-round"
[ngStyle]="{width: '2.5rem', height: '2.5rem'}">
<i class="pi pi-thumbs-up text-orange-500 text-xl"></i>
</div>
</div>
<!-- <span class="text-green-500 font-medium">%52+ </span>
<span class="text-500">since last week</span> -->
</div>
</div>
<div class="col-12 lg:col-6 xl:col-3">
<div class="card mb-0">
<div class="flex justify-content-between mb-3">
<div>
<span class="block text-500 font-medium mb-3">Best Difficulty</span>
<div class="text-900 font-medium text-xl">{{info.bestDiff}}</div>
</div>
<div class="flex align-items-center justify-content-center bg-yellow-100 border-round"
[ngStyle]="{width: '2.5rem', height: '2.5rem'}">
<i class="pi pi-star-fill text-yellow-500 text-xl"></i>
</div>
</div>
<!-- <span class="text-green-500 font-medium">520 </span>
<span class="text-500">newly registered</span> -->
</div>
</div>
<div class="container">
<div>
<div class="card">
<h2>Overview</h2>
<table *ngIf="info$ | async as info; else loading">
<tr>
<td>Model:</td>
<td>{{info.ASICModel}}</td>
</tr>
<tr>
<td>Uptime:</td>
<td>{{info.uptimeSeconds | dateAgo}}</td>
</tr>
<tr>
<td>WiFi Status:</td>
<td>{{info.wifiStatus}}</td>
</tr>
<tr>
<td>Free Heap Memory:</td>
<td>{{info.freeHeap}}</td>
</tr>
<tr>
<td>Version:</td>
<td>{{info.version}}</td>
</tr>
<tr>
<td>Board Version:</td>
<td>{{info.boardVersion}}</td>
</tr>
</table>
</div>
<p-chart [data]="chartData" [options]="chartOptions"></p-chart>
</div>
</div>
<div class="col-12 lg:col-12 xl:col-6">
<div class="card">
<h2>Pool Information</h2>
<h5>Power</h5>
<div class="grid text-center">
<div class="col-4">
<p-knob [min]="3" [max]="20" [readonly]="true" [(ngModel)]="info.power"
valueTemplate="{value}W"></p-knob>
Power
</div>
<div class="col-4">
<p-knob [min]="4.5" [max]="5.5" [readonly]="true" [(ngModel)]="info.voltage"
valueTemplate="{value}V"></p-knob>
Input Voltage
<span class="danger" *ngIf="info.voltage < 4.8">&nbsp; Danger: Low voltage</span>
</div>
<div class="col-4">
<p-knob [min]="0.9" [max]="1.8" [readonly]="true" [(ngModel)]="info.coreVoltage"
valueTemplate="{value}A"></p-knob>
ASIC Voltage Requested
</div>
</div>
</div>
</div>
<div class="col-12 lg:col-6 xl:col-3">
<div class="card">
<h5>Heat</h5>
<div class="grid text-center">
<div class="col-6">
<p-knob [min]="20" [max]="75" [readonly]="true" [(ngModel)]="info.temp"
valueTemplate="{value}C"></p-knob>
ASIC Temperature
<span class="danger" *ngIf="info.temp >= 70">&nbsp;
Danger:
High Temperature</span>
</div>
<div class="col-6">
<p-knob [min]="0" [max]="100" [readonly]="true" [(ngModel)]="info.fanspeed"
valueTemplate="{value}%"></p-knob>
Fan
</div>
<div class="col-6" *ngIf="info.boardtemp1">
<p-knob [min]="20" [max]="75" [readonly]="true" [(ngModel)]="info.boardtemp1"
valueTemplate="{value}C"></p-knob>
Board Temp 1
</div>
<div class="col-6" *ngIf="info.boardtemp2">
<p-knob [min]="20" [max]="75" [readonly]="true" [(ngModel)]="info.boardtemp2"
valueTemplate="{value}C"></p-knob>
Board Temp 2
</div>
</div>
</div>
</div>
<div class="col-12 lg:col-6 xl:col-3">
<div class="card">
<h5>Performance</h5>
<div class="grid text-center">
<div class="col-6">
<p-knob [min]="100" [max]="800" [readonly]="true" [(ngModel)]="info.frequency"
valueTemplate="{value}Mhz"></p-knob>
ASIC Frequency
</div>
<div class="col-6">
<p-knob [min]="0.9" [max]="1.8" [readonly]="true" [(ngModel)]="info.coreVoltageActual"
valueTemplate="{value}V"></p-knob>
ASIC Voltage Measured
</div>
</div>
</div>
</div>
<div class=" col-12 lg:col-6">
<div class="card">
<h5>Pool Information</h5>
<table *ngIf="info$ | async as info; else loading">
<tr *ngIf="quickLink$ | async as quickLink">
<td>Quick Link:</td>
<td><a style="color: white;" [href]="quickLink" target="_blank">{{quickLink}}</a></td>
<td><a style="color: white; text-decoration: underline;" [href]="quickLink"
target="_blank">{{quickLink}}</a></td>
</tr>
<tr>
<td>URL:</td>
@ -54,97 +192,13 @@
<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>
<div>
<div class="card">
<h2>Power</h2>
<table *ngIf="info$ | async as info; else loading">
<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> <span class="danger"
*ngIf="info.voltage < 4800">&nbsp; Danger: Low voltage</span></td>
</tr>
<tr>
<td>Regulator Current:</td>
<td>{{info.current | number: '0.0-0'}} <small>mA</small></td>
</tr>
<tr>
<td>Frequency:</td>
<td>{{info.frequency}} <small>Mhz</small></td>
</tr>
<tr>
<td>Core Voltage:</td>
<td>{{info.coreVoltage}} <small>mV</small></td>
</tr>
<tr>
<td>Measured Core Voltage:</td>
<td>{{info.coreVoltageActual}} <small>mV</small></td>
</tr>
<tr>
<td>Fan Speed:</td>
<td>{{info.fanSpeed}} <small>RPM</small></td>
</tr>
<tr>
<td>Chip Temperature:</td>
<td>{{info.temp}} <small>C</small> <span class="danger" *ngIf="info.temp >= 100">&nbsp; Danger:
High Temperature</span></td>
</tr>
<tr>
<td>Board Temperature 1:</td>
<td>{{info.boardtemp1}} <small>C</small> <span class="danger" *ngIf="info.boardtemp1 >= 60">&nbsp; Danger:
High Temperature</span></td>
</tr>
<tr>
<td>Board Temperature 2:</td>
<td>{{info.boardtemp2}} <small>C</small> <span class="danger" *ngIf="info.boardtemp1 >= 60">&nbsp; Danger:
High Temperature</span></td>
</tr>
</table>
</div>
<div class="card">
<h2>Results</h2>
<table *ngIf="info$ | async as info; else loading">
<tr>
<td> Hash Rate:</td>
<td>{{info.hashRate | number: '1.2-2'}} <small>Gh/s</small></td>
</tr>
<tr>
<td>Efficiency:</td>
<td>{{info.power / (info.hashRate/1000) | number: '1.2-2'}} <small>W/Th</small></td>
</tr>
<tr>
<td>Best Difficulty:</td>
<td>{{info.bestDiff}}</td>
</tr>
</table>
</div>
</div>
</div>
<div class="card mt-2">
<h2>Realtime Logs <button (click)="toggleLogs()" 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>
</ng-container>

View File

@ -1,56 +1,8 @@
.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;
margin-right: 1.5rem;
}
.restart {
margin-bottom: 1.5rem;
}
#logs {
height: 500px;
font-family: monospace;
border: 1px solid #304562;
overflow-y: scroll;
overflow-x: hidden;
>div {
max-width: 100%;
line-break: anywhere;
}
}
.danger {
color: red;
font-weight: bold;
}
.card {
min-height: 100%;
}

View File

@ -1,7 +1,8 @@
import { AfterViewChecked, Component, ElementRef, OnDestroy, ViewChild } from '@angular/core';
import { interval, map, Observable, shareReplay, startWith, Subscription, switchMap } from 'rxjs';
import { Component } from '@angular/core';
import { interval, map, Observable, shareReplay, startWith, switchMap, tap } from 'rxjs';
import { HashSuffixPipe } from 'src/app/pipes/hash-suffix.pipe';
import { SystemService } from 'src/app/services/system.service';
import { WebsocketService } from 'src/app/services/web-socket.service';
import { eASICModel } from 'src/models/enum/eASICModel';
import { ISystemInfo } from 'src/models/ISystemInfo';
@Component({
@ -9,33 +10,143 @@ import { ISystemInfo } from 'src/models/ISystemInfo';
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements AfterViewChecked, OnDestroy {
@ViewChild('scrollContainer') private scrollContainer!: ElementRef;
export class HomeComponent {
public info$: Observable<ISystemInfo>;
public quickLink$: Observable<string | undefined>;
public expectedHashRate$: Observable<number | undefined>;
public logs: string[] = [];
private websocketSubscription?: Subscription;
public showLogs = false;
public chartOptions: any;
public dataLabel: number[] = [];
public dataData: number[] = [];
public chartData?: any;
constructor(
private systemService: SystemService,
private websocketService: WebsocketService
private systemService: SystemService
) {
const documentStyle = getComputedStyle(document.documentElement);
const textColor = documentStyle.getPropertyValue('--text-color');
const textColorSecondary = documentStyle.getPropertyValue('--text-color-secondary');
const surfaceBorder = documentStyle.getPropertyValue('--surface-border');
const primaryColor = documentStyle.getPropertyValue('--primary-color');
this.chartData = {
labels: [],
datasets: [
{
type: 'line',
label: '10 Minute',
data: [],
fill: false,
backgroundColor: primaryColor,
borderColor: primaryColor,
tension: .4,
pointRadius: 1,
borderWidth: 1
},
]
};
this.chartOptions = {
animation: false,
maintainAspectRatio: false,
plugins: {
legend: {
labels: {
color: textColor
}
}
},
scales: {
x: {
type: 'time',
time: {
unit: 'hour', // Set the unit to 'minute'
},
ticks: {
color: textColorSecondary
},
grid: {
color: surfaceBorder,
drawBorder: false,
display: true
}
},
y: {
ticks: {
color: textColorSecondary,
callback: (value: number) => HashSuffixPipe.transform(value)
},
grid: {
color: surfaceBorder,
drawBorder: false
}
}
}
};
this.info$ = interval(5000).pipe(
startWith(() => this.systemService.getInfo()),
switchMap(() => {
return this.systemService.getInfo()
}),
tap(info => {
this.dataData.push(info.hashRate * 1000000000);
this.dataLabel.push(new Date().getTime());
if (this.dataData.length >= 1000) {
this.dataData.shift();
this.dataLabel.shift();
}
this.chartData.labels = this.dataLabel;
this.chartData.datasets[0].data = this.dataData;
this.chartData = {
...this.chartData
};
}),
map(info => {
info.power = parseFloat(info.power.toFixed(1))
info.voltage = parseFloat((info.voltage / 1000).toFixed(1));
info.current = parseFloat((info.current / 1000).toFixed(1));
info.coreVoltageActual = parseFloat((info.coreVoltageActual / 1000).toFixed(2));
info.coreVoltage = parseFloat((info.coreVoltage / 1000).toFixed(2));
return info;
}),
shareReplay({ refCount: true, bufferSize: 1 })
);
this.expectedHashRate$ = this.info$.pipe(map(info => {
if (info.ASICModel === eASICModel.BM1366) {
const version = parseInt(info.boardVersion);
if (version >= 400 && version < 500) {
return (info.frequency * ((894 * 6) / 1000))
} else {
return (info.frequency * (894 / 1000))
}
} else if (info.ASICModel === eASICModel.BM1397) {
return (info.frequency * (672 / 1000))
}
return undefined;
}))
this.quickLink$ = this.info$.pipe(
map(info => {
if (info.stratumURL.includes('public-pool.io')) {
@ -49,32 +160,9 @@ export class HomeComponent implements AfterViewChecked, OnDestroy {
}
ngOnDestroy(): void {
this.websocketSubscription?.unsubscribe();
}
ngAfterViewChecked(): void {
if (this.scrollContainer?.nativeElement != null) {
this.scrollContainer.nativeElement.scrollTo({ left: 0, top: this.scrollContainer.nativeElement.scrollHeight, behavior: 'smooth' });
}
}
public toggleLogs() {
this.showLogs = !this.showLogs;
if (this.showLogs) {
this.websocketSubscription = this.websocketService.ws$.subscribe({
next: (val) => {
this.logs.push(val);
if (this.logs.length > 100) {
this.logs.shift();
}
}
})
} else {
this.websocketSubscription?.unsubscribe();
}
}
}

View File

@ -0,0 +1,47 @@
<div class="grid">
<div class="col-12 lg:col-6">
<div class="card">
<h5>Overview</h5>
<table *ngIf="info$ | async as info">
<tr>
<td>Model:</td>
<td>{{info.ASICModel}}</td>
</tr>
<tr>
<td>Uptime:</td>
<td>{{info.uptimeSeconds | dateAgo}}</td>
</tr>
<tr>
<td>WiFi Status:</td>
<td>{{info.wifiStatus}}</td>
</tr>
<tr>
<td>Free Heap Memory:</td>
<td>{{info.freeHeap}}</td>
</tr>
<tr>
<td>Version:</td>
<td>{{info.version}}</td>
</tr>
<tr>
<td>Board Version:</td>
<td>{{info.boardVersion}}</td>
</tr>
</table>
</div>
</div>
<div class="col-12">
<div class="card">
<h2>Realtime Logs <button pButton (click)="toggleLogs()" 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>
</div>
</div>

View File

@ -0,0 +1,13 @@
#logs {
height: 500px;
font-family: monospace;
border: 1px solid #304562;
overflow-y: scroll;
overflow-x: hidden;
>div {
max-width: 100%;
line-break: anywhere;
}
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LogsComponent } from './logs.component';
describe('LogsComponent', () => {
let component: LogsComponent;
let fixture: ComponentFixture<LogsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [LogsComponent]
})
.compileComponents();
fixture = TestBed.createComponent(LogsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,73 @@
import { AfterViewChecked, Component, ElementRef, OnDestroy, ViewChild } from '@angular/core';
import { interval, map, Observable, shareReplay, startWith, Subscription, switchMap } from 'rxjs';
import { SystemService } from 'src/app/services/system.service';
import { WebsocketService } from 'src/app/services/web-socket.service';
import { ISystemInfo } from 'src/models/ISystemInfo';
@Component({
selector: 'app-logs',
templateUrl: './logs.component.html',
styleUrl: './logs.component.scss'
})
export class LogsComponent implements OnDestroy, AfterViewChecked {
@ViewChild('scrollContainer') private scrollContainer!: ElementRef;
public info$: Observable<ISystemInfo>;
public logs: string[] = [];
private websocketSubscription?: Subscription;
public showLogs = false;
constructor(
private websocketService: WebsocketService,
private systemService: SystemService
) {
this.info$ = interval(5000).pipe(
startWith(() => this.systemService.getInfo()),
switchMap(() => {
return this.systemService.getInfo()
}),
map(info => {
info.power = parseFloat(info.power.toFixed(1))
info.voltage = parseFloat((info.voltage / 1000).toFixed(1));
info.current = parseFloat((info.current / 1000).toFixed(1));
info.coreVoltageActual = parseFloat((info.coreVoltageActual / 1000).toFixed(2));
info.coreVoltage = parseFloat((info.coreVoltage / 1000).toFixed(2));
return info;
}),
shareReplay({ refCount: true, bufferSize: 1 })
);
}
ngOnDestroy(): void {
this.websocketSubscription?.unsubscribe();
}
public toggleLogs() {
this.showLogs = !this.showLogs;
if (this.showLogs) {
this.websocketSubscription = this.websocketService.ws$.subscribe({
next: (val) => {
this.logs.push(val);
if (this.logs.length > 100) {
this.logs.shift();
}
}
})
} else {
this.websocketSubscription?.unsubscribe();
}
}
ngAfterViewChecked(): void {
if (this.scrollContainer?.nativeElement != null) {
this.scrollContainer.nativeElement.scrollTo({ left: 0, top: this.scrollContainer.nativeElement.scrollHeight, behavior: 'smooth' });
}
}
}

View File

@ -6,33 +6,42 @@
</div>
<div class="card" *ngIf="latestRelease$ | async as latestRelease">
<h2>Latest Release: {{latestRelease.name}}</h2>
<div class="grid">
<div class="col-12 lg:col-6 xl:col-4">
<div class="card" *ngIf="latestRelease$ | async as latestRelease">
<h2>Latest Release: {{latestRelease.name}}</h2>
<div *ngFor="let asset of latestRelease.assets">
<div *ngIf="asset.name == 'esp-miner.bin'">
<a target="_blank" [href]="asset.browser_download_url">esp-miner.bin</a>
</div>
<div *ngIf="asset.name == 'www.bin'">
<a target="_blank" [href]="asset.browser_download_url">www.bin</a>
<div *ngFor="let asset of latestRelease.assets">
<div *ngIf="asset.name == 'esp-miner.bin'">
<a style="text-decoration: underline;" target="_blank"
[href]="asset.browser_download_url">esp-miner.bin</a>
</div>
<div *ngIf="asset.name == 'www.bin'">
<a style="text-decoration: underline;" target="_blank"
[href]="asset.browser_download_url">www.bin</a>
</div>
</div>
</div>
</div>
<div class="col-12 lg:col-6 xl:col-4">
<div class="card">
<h2>Update Firmware <span *ngIf="firmwareUpdateProgress != null">{{firmwareUpdateProgress}}%</span></h2>
<!-- <input type="file" id="file" (change)="otaUpdate($event)" accept=".bin"> -->
<p-fileUpload [customUpload]="true" mode="basic" accept=".bin" (uploadHandler)="otaUpdate($event)"
[auto]="true" chooseLabel="Browse"></p-fileUpload>
<small>(esp-miner.bin)</small>
</div>
</div>
</div>
<div class="col-12 lg:col-12 xl:col-4">
<div class="card">
<h2>Update Website <span *ngIf="websiteUpdateProgress != null">{{websiteUpdateProgress}}%</span></h2>
<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>
<p-fileUpload [customUpload]="true" mode="basic" accept=".bin" (uploadHandler)="otaWWWUpdate($event)"
[auto]="true" chooseLabel="Browse"></p-fileUpload>
<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>
<small>(www.bin)</small>
</div>
</div>
</div>

View File

@ -2,6 +2,7 @@ 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 { FileUploadHandlerEvent } from 'primeng/fileupload';
import { map, Observable, startWith } from 'rxjs';
import { GithubUpdateService } from 'src/app/services/github-update.service';
import { LoadingService } from 'src/app/services/loading.service';
@ -129,8 +130,8 @@ export class SettingsComponent {
});
}
otaUpdate(event: any) {
const file = event.target?.files.item(0) as File;
otaUpdate(event: FileUploadHandlerEvent) {
const file = event.files[0];
if (file.name != 'esp-miner.bin') {
this.toastrService.error('Incorrect file, looking for esp-miner.bin.', 'Error');
@ -153,15 +154,16 @@ export class SettingsComponent {
}
},
error: (err) => {
this.toastrService.error(event.statusText, 'Error');
this.toastrService.error('Uploaded Error', 'Error');
},
complete: () => {
this.firmwareUpdateProgress = null;
}
});
}
otaWWWUpdate(event: any) {
const file = event.target?.files.item(0) as File;
otaWWWUpdate(event: FileUploadHandlerEvent) {
const file = event.files[0];
if (file.name != 'www.bin') {
this.toastrService.error('Incorrect file, looking for www.bin.', 'Error');
return;
@ -187,7 +189,7 @@ export class SettingsComponent {
}
},
error: (err) => {
this.toastrService.error(event.statusText, 'Error');
this.toastrService.error('Upload Error', 'Error');
},
complete: () => {
this.websiteUpdateProgress = null;

View File

@ -1,11 +1,20 @@
<form [formGroup]="form">
<label>AxeOS Device IP: </label>
<input formControlName="ip" type="text">
<button style="margin-left: 15px;" class="btn btn-primary" (click)="add()" [disabled]="form.invalid">Add</button>
<!-- <input type="checkbox" formControlName="sync"> Sync All -->
</form>
<div class="card">
<form [formGroup]="form">
<div class="field grid p-fluid">
<label htmlFor="ip" class="col-12 mb-2 md:col-2 md:mb-0">AxeOS Device IP</label>
<div class="col-12 md:col-10">
<p-inputGroup>
<input pInputText id="ip" formControlName="ip" type="text" />
<button pButton (click)="add()" [disabled]="form.invalid">Add</button>
</p-inputGroup>
</div>
</div>
</form>
</div>
<div>
<button class="btn btn-primary" (click)="refresh()">Refresh</button>
<button pButton (click)="refresh()">Refresh</button>
</div>
<div>
<table cellspacing="0" cellpadding="0" *ngIf="swarm$ | async as swarm">
@ -32,9 +41,9 @@
<td>{{axe.temp}} <small>C</small></td>
<td>{{axe.bestDiff}}</td>
<td>{{axe.version}}</td>
<td><button class="btn btn-primary" (click)="edit(axe)">&#9998;</button></td>
<td><button class="btn btn-danger" (click)="restart(axe)">&#9850;</button></td>
<td><button class="btn btn-secondary" (click)="remove(axe)">&#10006;</button></td>
<td><p-button icon="pi pi-pencil" pp-button (click)="edit(axe)"></p-button></td>
<td><p-button icon="pi pi-sync" pp-button severity="danger" (click)="restart(axe)"></p-button></td>
<td><p-button icon="pi pi-trash" pp-button severity="secondary" (click)="remove(axe)"></p-button></td>
</tr>
</ng-container>
</table>

View File

@ -61,13 +61,18 @@ export class SwarmComponent {
const newIp = this.form.value.ip;
combineLatest([this.systemService.getSwarmInfo('http://' + newIp), this.systemService.getSwarmInfo()]).pipe(
switchMap(([newSwarmInfo, swarmInfo]) => {
switchMap(([newSwarmInfo, existingSwarmInfo]) => {
const swarmUpdate = swarmInfo.map(({ ip }) => {
return this.systemService.updateSwarm('http://' + ip, [{ ip: newIp }, ...newSwarmInfo, ...swarmInfo])
if (existingSwarmInfo.length < 1) {
existingSwarmInfo.push({ ip: window.location.host });
}
const swarmUpdate = existingSwarmInfo.map(({ ip }) => {
return this.systemService.updateSwarm('http://' + ip, [{ ip: newIp }, ...newSwarmInfo, ...existingSwarmInfo])
});
const newAxeOs = this.systemService.updateSwarm('http://' + newIp, [{ ip: newIp }, ...swarmInfo])
const newAxeOs = this.systemService.updateSwarm('http://' + newIp, [{ ip: newIp }, ...existingSwarmInfo])
return forkJoin([newAxeOs, ...swarmUpdate]);

View File

@ -0,0 +1,4 @@
export interface MenuChangeEvent {
key: string;
routeEvent?: boolean;
}

View File

@ -0,0 +1,3 @@
<div class="layout-footer">
</div>

View File

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
import { LayoutService } from "./service/app.layout.service";
@Component({
selector: 'app-footer',
templateUrl: './app.footer.component.html'
})
export class AppFooterComponent {
constructor(public layoutService: LayoutService) { }
}

View File

@ -0,0 +1,14 @@
<div class="layout-wrapper" [ngClass]="containerClass">
<app-topbar></app-topbar>
<div class="layout-sidebar">
<app-sidebar></app-sidebar>
</div>
<div class="layout-main-container">
<div class="layout-main">
<router-outlet></router-outlet>
</div>
<app-footer></app-footer>
</div>
<div class="layout-mask"></div>
</div>

View File

@ -0,0 +1,121 @@
import { Component, OnDestroy, Renderer2, ViewChild } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { filter, Subscription } from 'rxjs';
import { LayoutService } from "./service/app.layout.service";
import { AppSidebarComponent } from "./app.sidebar.component";
import { AppTopBarComponent } from './app.topbar.component';
@Component({
selector: 'app-layout',
templateUrl: './app.layout.component.html'
})
export class AppLayoutComponent implements OnDestroy {
overlayMenuOpenSubscription: Subscription;
menuOutsideClickListener: any;
profileMenuOutsideClickListener: any;
@ViewChild(AppSidebarComponent) appSidebar!: AppSidebarComponent;
@ViewChild(AppTopBarComponent) appTopbar!: AppTopBarComponent;
constructor(public layoutService: LayoutService, public renderer: Renderer2, public router: Router) {
this.overlayMenuOpenSubscription = this.layoutService.overlayOpen$.subscribe(() => {
if (!this.menuOutsideClickListener) {
this.menuOutsideClickListener = this.renderer.listen('document', 'click', event => {
const isOutsideClicked = !(this.appSidebar.el.nativeElement.isSameNode(event.target) || this.appSidebar.el.nativeElement.contains(event.target)
|| this.appTopbar.menuButton.nativeElement.isSameNode(event.target) || this.appTopbar.menuButton.nativeElement.contains(event.target));
if (isOutsideClicked) {
this.hideMenu();
}
});
}
if (!this.profileMenuOutsideClickListener) {
this.profileMenuOutsideClickListener = this.renderer.listen('document', 'click', event => {
const isOutsideClicked = !(this.appTopbar.menu.nativeElement.isSameNode(event.target) || this.appTopbar.menu.nativeElement.contains(event.target)
|| this.appTopbar.topbarMenuButton.nativeElement.isSameNode(event.target) || this.appTopbar.topbarMenuButton.nativeElement.contains(event.target));
if (isOutsideClicked) {
this.hideProfileMenu();
}
});
}
if (this.layoutService.state.staticMenuMobileActive) {
this.blockBodyScroll();
}
});
this.router.events.pipe(filter(event => event instanceof NavigationEnd))
.subscribe(() => {
this.hideMenu();
this.hideProfileMenu();
});
}
hideMenu() {
this.layoutService.state.overlayMenuActive = false;
this.layoutService.state.staticMenuMobileActive = false;
this.layoutService.state.menuHoverActive = false;
if (this.menuOutsideClickListener) {
this.menuOutsideClickListener();
this.menuOutsideClickListener = null;
}
this.unblockBodyScroll();
}
hideProfileMenu() {
this.layoutService.state.profileSidebarVisible = false;
if (this.profileMenuOutsideClickListener) {
this.profileMenuOutsideClickListener();
this.profileMenuOutsideClickListener = null;
}
}
blockBodyScroll(): void {
if (document.body.classList) {
document.body.classList.add('blocked-scroll');
}
else {
document.body.className += ' blocked-scroll';
}
}
unblockBodyScroll(): void {
if (document.body.classList) {
document.body.classList.remove('blocked-scroll');
}
else {
document.body.className = document.body.className.replace(new RegExp('(^|\\b)' +
'blocked-scroll'.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
}
}
get containerClass() {
return {
'layout-theme-light': this.layoutService.config().colorScheme === 'light',
'layout-theme-dark': this.layoutService.config().colorScheme === 'dark',
'layout-overlay': this.layoutService.config().menuMode === 'overlay',
'layout-static': this.layoutService.config().menuMode === 'static',
'layout-static-inactive': this.layoutService.state.staticMenuDesktopInactive && this.layoutService.config().menuMode === 'static',
'layout-overlay-active': this.layoutService.state.overlayMenuActive,
'layout-mobile-active': this.layoutService.state.staticMenuMobileActive,
'p-input-filled': this.layoutService.config().inputStyle === 'filled',
'p-ripple-disabled': !this.layoutService.config().ripple
}
}
ngOnDestroy() {
if (this.overlayMenuOpenSubscription) {
this.overlayMenuOpenSubscription.unsubscribe();
}
if (this.menuOutsideClickListener) {
this.menuOutsideClickListener();
}
}
}

View File

@ -0,0 +1,47 @@
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule } from '@angular/router';
import { BadgeModule } from 'primeng/badge';
import { InputSwitchModule } from 'primeng/inputswitch';
import { InputTextModule } from 'primeng/inputtext';
import { RadioButtonModule } from 'primeng/radiobutton';
import { RippleModule } from 'primeng/ripple';
import { SidebarModule } from 'primeng/sidebar';
import { PrimeNGModule } from '../prime-ng.module';
import { AppFooterComponent } from './app.footer.component';
import { AppLayoutComponent } from './app.layout.component';
import { AppMenuComponent } from './app.menu.component';
import { AppMenuitemComponent } from './app.menuitem.component';
import { AppSidebarComponent } from './app.sidebar.component';
import { AppTopBarComponent } from './app.topbar.component';
@NgModule({
declarations: [
AppMenuitemComponent,
AppTopBarComponent,
AppFooterComponent,
AppMenuComponent,
AppSidebarComponent,
AppLayoutComponent,
],
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
BrowserAnimationsModule,
InputTextModule,
SidebarModule,
BadgeModule,
RadioButtonModule,
InputSwitchModule,
RippleModule,
RouterModule,
PrimeNGModule
],
exports: [AppLayoutComponent]
})
export class AppLayoutModule { }

View File

@ -0,0 +1,7 @@
<ul class="layout-menu">
<ng-container *ngFor="let item of model; let i = index;">
<li app-menuitem *ngIf="!item.separator" [item]="item" [index]="i" [root]="true"></li>
<li *ngIf="item.separator" class="menu-separator"></li>
</ng-container>
</ul>

View File

@ -0,0 +1,30 @@
import { Component, OnInit } from '@angular/core';
import { LayoutService } from './service/app.layout.service';
@Component({
selector: 'app-menu',
templateUrl: './app.menu.component.html'
})
export class AppMenuComponent implements OnInit {
model: any[] = [];
constructor(public layoutService: LayoutService) { }
ngOnInit() {
this.model = [
{
label: 'Menu',
items: [
{ label: 'Dashboard', icon: 'pi pi-fw pi-home', routerLink: ['/'] },
{ label: 'Swarm', icon: 'pi pi-fw pi-share-alt', routerLink: ['swarm'] },
{ label: 'Settings', icon: 'pi pi-fw pi-cog', routerLink: ['settings'] },
{ label: 'Logs', icon: 'pi pi-fw pi-list', routerLink: ['logs'] },
]
}
];
}
}

View File

@ -0,0 +1,23 @@
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { MenuChangeEvent } from './api/menuchangeevent';
@Injectable({
providedIn: 'root'
})
export class MenuService {
private menuSource = new Subject<MenuChangeEvent>();
private resetSource = new Subject();
menuSource$ = this.menuSource.asObservable();
resetSource$ = this.resetSource.asObservable();
onMenuStateChange(event: MenuChangeEvent) {
this.menuSource.next(event);
}
reset() {
this.resetSource.next(true);
}
}

View File

@ -0,0 +1,148 @@
import { ChangeDetectorRef, Component, Host, HostBinding, Input, OnDestroy, OnInit } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { animate, state, style, transition, trigger } from '@angular/animations';
import { Subscription } from 'rxjs';
import { filter } from 'rxjs/operators';
import { MenuService } from './app.menu.service';
import { LayoutService } from './service/app.layout.service';
@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: '[app-menuitem]',
template: `
<ng-container>
<div *ngIf="root && item.visible !== false" class="layout-menuitem-root-text">{{item.label}}</div>
<a *ngIf="(!item.routerLink || item.items) && item.visible !== false" [attr.href]="item.url" (click)="itemClick($event)"
[ngClass]="item.class" [attr.target]="item.target" tabindex="0" pRipple>
<i [ngClass]="item.icon" class="layout-menuitem-icon"></i>
<span class="layout-menuitem-text">{{item.label}}</span>
<i class="pi pi-fw pi-angle-down layout-submenu-toggler" *ngIf="item.items"></i>
</a>
<a *ngIf="(item.routerLink && !item.items) && item.visible !== false" (click)="itemClick($event)" [ngClass]="item.class"
[routerLink]="item.routerLink" routerLinkActive="active-route" [routerLinkActiveOptions]="item.routerLinkActiveOptions||{ paths: 'exact', queryParams: 'ignored', matrixParams: 'ignored', fragment: 'ignored' }"
[fragment]="item.fragment" [queryParamsHandling]="item.queryParamsHandling" [preserveFragment]="item.preserveFragment"
[skipLocationChange]="item.skipLocationChange" [replaceUrl]="item.replaceUrl" [state]="item.state" [queryParams]="item.queryParams"
[attr.target]="item.target" tabindex="0" pRipple>
<i [ngClass]="item.icon" class="layout-menuitem-icon"></i>
<span class="layout-menuitem-text">{{item.label}}</span>
<i class="pi pi-fw pi-angle-down layout-submenu-toggler" *ngIf="item.items"></i>
</a>
<ul *ngIf="item.items && item.visible !== false" [@children]="submenuAnimation">
<ng-template ngFor let-child let-i="index" [ngForOf]="item.items">
<li app-menuitem [item]="child" [index]="i" [parentKey]="key" [class]="child.badgeClass"></li>
</ng-template>
</ul>
</ng-container>
`,
animations: [
trigger('children', [
state('collapsed', style({
height: '0'
})),
state('expanded', style({
height: '*'
})),
transition('collapsed <=> expanded', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)'))
])
]
})
export class AppMenuitemComponent implements OnInit, OnDestroy {
@Input() item: any;
@Input() index!: number;
@Input() @HostBinding('class.layout-root-menuitem') root!: boolean;
@Input() parentKey!: string;
active = false;
menuSourceSubscription: Subscription;
menuResetSubscription: Subscription;
key: string = "";
constructor(public layoutService: LayoutService, private cd: ChangeDetectorRef, public router: Router, private menuService: MenuService) {
this.menuSourceSubscription = this.menuService.menuSource$.subscribe(value => {
Promise.resolve(null).then(() => {
if (value.routeEvent) {
this.active = (value.key === this.key || value.key.startsWith(this.key + '-')) ? true : false;
}
else {
if (value.key !== this.key && !value.key.startsWith(this.key + '-')) {
this.active = false;
}
}
});
});
this.menuResetSubscription = this.menuService.resetSource$.subscribe(() => {
this.active = false;
});
this.router.events.pipe(filter(event => event instanceof NavigationEnd))
.subscribe(params => {
if (this.item.routerLink) {
this.updateActiveStateFromRoute();
}
});
}
ngOnInit() {
this.key = this.parentKey ? this.parentKey + '-' + this.index : String(this.index);
if (this.item.routerLink) {
this.updateActiveStateFromRoute();
}
}
updateActiveStateFromRoute() {
let activeRoute = this.router.isActive(this.item.routerLink[0], { paths: 'exact', queryParams: 'ignored', matrixParams: 'ignored', fragment: 'ignored' });
if (activeRoute) {
this.menuService.onMenuStateChange({ key: this.key, routeEvent: true });
}
}
itemClick(event: Event) {
// avoid processing disabled items
if (this.item.disabled) {
event.preventDefault();
return;
}
// execute command
if (this.item.command) {
this.item.command({ originalEvent: event, item: this.item });
}
// toggle active state
if (this.item.items) {
this.active = !this.active;
}
this.menuService.onMenuStateChange({ key: this.key });
}
get submenuAnimation() {
return this.root ? 'expanded' : (this.active ? 'expanded' : 'collapsed');
}
@HostBinding('class.active-menuitem')
get activeClass() {
return this.active && !this.root;
}
ngOnDestroy() {
if (this.menuSourceSubscription) {
this.menuSourceSubscription.unsubscribe();
}
if (this.menuResetSubscription) {
this.menuResetSubscription.unsubscribe();
}
}
}

View File

@ -0,0 +1 @@
<app-menu></app-menu>

View File

@ -0,0 +1,11 @@
import { Component, ElementRef } from '@angular/core';
import { LayoutService } from "./service/app.layout.service";
@Component({
selector: 'app-sidebar',
templateUrl: './app.sidebar.component.html'
})
export class AppSidebarComponent {
constructor(public layoutService: LayoutService, public el: ElementRef) { }
}

View File

@ -0,0 +1,15 @@
<div class="layout-topbar">
<a class="layout-topbar-logo" routerLink="">
<div [routerLink]="['/']" class="header-text">Axe<span class="os">OS</span></div>
</a>
<button #menubutton class="p-link layout-menu-button layout-topbar-button" (click)="layoutService.onMenuToggle()">
<i class="pi pi-bars"></i>
</button>
<div class="layout-topbar-menu">
<p-button (click)="restart()" label="Restart" severity="danger"></p-button>
</div>
</div>

View File

@ -0,0 +1,36 @@
import { Component, ElementRef, ViewChild } from '@angular/core';
import { ToastrService } from 'ngx-toastr';
import { MenuItem } from 'primeng/api';
import { SystemService } from '../services/system.service';
import { LayoutService } from './service/app.layout.service';
@Component({
selector: 'app-topbar',
templateUrl: './app.topbar.component.html'
})
export class AppTopBarComponent {
items!: MenuItem[];
@ViewChild('menubutton') menuButton!: ElementRef;
@ViewChild('topbarmenubutton') topbarMenuButton!: ElementRef;
@ViewChild('topbarmenu') menu!: ElementRef;
constructor(public layoutService: LayoutService,
private systemService: SystemService,
private toastr: ToastrService
) { }
public restart() {
this.systemService.restart().subscribe(res => {
});
this.toastr.success('Success!', 'Bitaxe restarted');
}
}

View File

@ -0,0 +1,159 @@
import { Injectable, effect, signal } from '@angular/core';
import { Subject } from 'rxjs';
export interface AppConfig {
inputStyle: string;
colorScheme: string;
theme: string;
ripple: boolean;
menuMode: string;
scale: number;
}
interface LayoutState {
staticMenuDesktopInactive: boolean;
overlayMenuActive: boolean;
profileSidebarVisible: boolean;
configSidebarVisible: boolean;
staticMenuMobileActive: boolean;
menuHoverActive: boolean;
}
@Injectable({
providedIn: 'root',
})
export class LayoutService {
_config: AppConfig = {
ripple: false,
inputStyle: 'outlined',
menuMode: 'static',
colorScheme: 'light',
theme: 'lara-light-indigo',
scale: 14,
};
config = signal<AppConfig>(this._config);
state: LayoutState = {
staticMenuDesktopInactive: false,
overlayMenuActive: false,
profileSidebarVisible: false,
configSidebarVisible: false,
staticMenuMobileActive: false,
menuHoverActive: false,
};
private configUpdate = new Subject<AppConfig>();
private overlayOpen = new Subject<any>();
configUpdate$ = this.configUpdate.asObservable();
overlayOpen$ = this.overlayOpen.asObservable();
constructor() {
effect(() => {
const config = this.config();
if (this.updateStyle(config)) {
this.changeTheme();
}
this.changeScale(config.scale);
this.onConfigUpdate();
});
}
updateStyle(config: AppConfig) {
return (
config.theme !== this._config.theme ||
config.colorScheme !== this._config.colorScheme
);
}
onMenuToggle() {
if (this.isOverlay()) {
this.state.overlayMenuActive = !this.state.overlayMenuActive;
if (this.state.overlayMenuActive) {
this.overlayOpen.next(null);
}
}
if (this.isDesktop()) {
this.state.staticMenuDesktopInactive =
!this.state.staticMenuDesktopInactive;
} else {
this.state.staticMenuMobileActive =
!this.state.staticMenuMobileActive;
if (this.state.staticMenuMobileActive) {
this.overlayOpen.next(null);
}
}
}
showProfileSidebar() {
this.state.profileSidebarVisible = !this.state.profileSidebarVisible;
if (this.state.profileSidebarVisible) {
this.overlayOpen.next(null);
}
}
showConfigSidebar() {
this.state.configSidebarVisible = true;
}
isOverlay() {
return this.config().menuMode === 'overlay';
}
isDesktop() {
return window.innerWidth > 991;
}
isMobile() {
return !this.isDesktop();
}
onConfigUpdate() {
this._config = { ...this.config() };
this.configUpdate.next(this.config());
}
changeTheme() {
const config = this.config();
const themeLink = <HTMLLinkElement>document.getElementById('theme-css');
const themeLinkHref = themeLink.getAttribute('href')!;
const newHref = themeLinkHref
.split('/')
.map((el) =>
el == this._config.theme
? (el = config.theme)
: el == `theme-${this._config.colorScheme}`
? (el = `theme-${config.colorScheme}`)
: el
)
.join('/');
this.replaceThemeLink(newHref);
}
replaceThemeLink(href: string) {
const id = 'theme-css';
let themeLink = <HTMLLinkElement>document.getElementById(id);
const cloneLinkElement = <HTMLLinkElement>themeLink.cloneNode(true);
cloneLinkElement.setAttribute('href', href);
cloneLinkElement.setAttribute('id', id + '-clone');
themeLink.parentNode!.insertBefore(
cloneLinkElement,
themeLink.nextSibling
);
cloneLinkElement.addEventListener('load', () => {
themeLink.remove();
cloneLinkElement.setAttribute('id', id);
});
}
changeScale(value: number) {
document.documentElement.style.fontSize = `${value}px`;
}
}

View File

@ -0,0 +1,42 @@
.layout-config-button {
display: block;
position: fixed;
width: 3rem;
height: 3rem;
line-height: 3rem;
background: var(--primary-color);
color: var(--primary-color-text);
text-align: center;
top: 50%;
right: 0;
margin-top: -1.5rem;
border-top-left-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius);
border-top-right-radius: 0;
border-bottom-right-radius: 0;
transition: background-color var(--transition-duration);
overflow: hidden;
cursor: pointer;
z-index: 999;
box-shadow: -.25rem 0 1rem rgba(0,0,0,.15);
i {
font-size: 2rem;
line-height: inherit;
transform: rotate(0deg);
transition: transform 1s;
}
&:hover {
background: var(--primary-400);
}
}
.layout-config-sidebar {
&.p-sidebar {
.p-sidebar-content {
padding-left: 2rem;
padding-right: 2rem;
}
}
}

View File

@ -0,0 +1,12 @@
.layout-main-container {
display: flex;
flex-direction: column;
min-height: 100vh;
justify-content: space-between;
padding: 7rem 2rem 2rem 4rem;
transition: margin-left $transitionDuration;
}
.layout-main {
flex: 1 1 auto;
}

View File

@ -0,0 +1,8 @@
.layout-footer {
transition: margin-left $transitionDuration;
display: flex;
align-items: center;
justify-content: center;
padding-top: 1rem;
border-top: 1px solid var(--surface-border);
}

View File

@ -0,0 +1,28 @@
* {
box-sizing: border-box;
}
html {
height: 100%;
font-size: $scale;
}
body {
font-family: var(--font-family);
color: var(--text-color);
background-color: var(--surface-ground);
margin: 0;
padding: 0;
min-height: 100%;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
text-decoration: none;
color: var(--primary-color);
}
.layout-wrapper {
min-height: 100vh;
}

View File

@ -0,0 +1,135 @@
.layout-sidebar {
position: fixed;
width: 300px;
height: calc(100vh - 9rem);
z-index: 999;
overflow-y: auto;
user-select: none;
top: 7rem;
left: 2rem;
transition: transform $transitionDuration, left $transitionDuration;
background-color: var(--surface-overlay);
border-radius: $borderRadius;
padding: 0.5rem 1.5rem;
box-shadow: 0px 3px 5px rgba(0, 0, 0, .02), 0px 0px 2px rgba(0, 0, 0, .05), 0px 1px 4px rgba(0, 0, 0, .08);
}
.layout-menu {
margin: 0;
padding: 0;
list-style-type: none;
.layout-root-menuitem {
>.layout-menuitem-root-text {
font-size: .857rem;
text-transform: uppercase;
font-weight: 700;
color: var(--surface-900);
margin: .75rem 0;
}
>a {
display: none;
}
}
a {
user-select: none;
&.active-menuitem {
>.layout-submenu-toggler {
transform: rotate(-180deg);
}
}
}
li.active-menuitem {
>a {
.layout-submenu-toggler {
transform: rotate(-180deg);
}
}
}
ul {
margin: 0;
padding: 0;
list-style-type: none;
a {
display: flex;
align-items: center;
position: relative;
outline: 0 none;
color: var(--text-color);
cursor: pointer;
padding: .75rem 1rem;
border-radius: $borderRadius;
transition: background-color $transitionDuration, box-shadow $transitionDuration;
.layout-menuitem-icon {
margin-right: .5rem;
}
.layout-submenu-toggler {
font-size: 75%;
margin-left: auto;
transition: transform $transitionDuration;
}
&.active-route {
font-weight: 700;
color: var(--primary-color);
}
&:hover {
background-color: var(--surface-hover);
}
&:focus {
@include focused-inset();
}
}
ul {
overflow: hidden;
border-radius: $borderRadius;
li {
a {
margin-left: 1rem;
}
li {
a {
margin-left: 2rem;
}
li {
a {
margin-left: 2.5rem;
}
li {
a {
margin-left: 3rem;
}
li {
a {
margin-left: 3.5rem;
}
li {
a {
margin-left: 4rem;
}
}
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,13 @@
@mixin focused() {
outline: 0 none;
outline-offset: 0;
transition: box-shadow .2s;
box-shadow: var(--focus-ring);
}
@mixin focused-inset() {
outline: 0 none;
outline-offset: 0;
transition: box-shadow .2s;
box-shadow: inset var(--focus-ring);
}

View File

@ -0,0 +1,51 @@
.preloader {
position: fixed;
z-index: 999999;
background: #edf1f5;
width: 100%;
height: 100%;
}
.preloader-content {
border: 0 solid transparent;
border-radius: 50%;
width: 150px;
height: 150px;
position: absolute;
top: calc(50vh - 75px);
left: calc(50vw - 75px);
}
.preloader-content:before,
.preloader-content:after {
content: '';
border: 1em solid var(--primary-color);
border-radius: 50%;
width: inherit;
height: inherit;
position: absolute;
top: 0;
left: 0;
animation: loader 2s linear infinite;
opacity: 0;
}
.preloader-content:before {
animation-delay: 0.5s;
}
@keyframes loader {
0% {
transform: scale(0);
opacity: 0;
}
50% {
opacity: 1;
}
100% {
transform: scale(1);
opacity: 0;
}
}

View File

@ -0,0 +1,100 @@
@media screen and (min-width: 1960px) {
.layout-main, .landing-wrapper {
width: 1504px;
margin-left: auto !important;
margin-right: auto !important;
}
}
@media (min-width: 992px) {
.layout-wrapper {
&.layout-overlay {
.layout-main-container {
margin-left: 0;
padding-left: 2rem;
}
.layout-sidebar {
transform: translateX(-100%);
left: 0;
top: 0;
height: 100vh;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
&.layout-overlay-active {
.layout-sidebar {
transform: translateX(0);
}
}
}
&.layout-static {
.layout-main-container {
margin-left: 300px;
}
&.layout-static-inactive {
.layout-sidebar {
transform: translateX(-100%);
left: 0;
}
.layout-main-container {
margin-left: 0;
padding-left: 2rem;
}
}
}
.layout-mask {
display: none;
}
}
}
@media (max-width: 991px) {
.blocked-scroll {
overflow: hidden;
}
.layout-wrapper {
.layout-main-container {
margin-left: 0;
padding-left: 2rem;
}
.layout-sidebar {
transform: translateX(-100%);
left: 0;
top: 0;
height: 100vh;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.layout-mask {
display: none;
position: fixed;
top: 0;
left: 0;
z-index: 998;
width: 100%;
height: 100%;
background-color: var(--maskbg);
}
&.layout-mobile-active {
.layout-sidebar {
transform: translateX(0);
}
.layout-mask {
display: block;
animation: fadein $transitionDuration;
}
}
}
}

View File

@ -0,0 +1,149 @@
.layout-topbar {
position: fixed;
height: 5rem;
z-index: 997;
left: 0;
top: 0;
width: 100%;
padding: 0 2rem;
background-color: var(--surface-card);
transition: left $transitionDuration;
display: flex;
align-items: center;
box-shadow: 0px 3px 5px rgba(0,0,0,.02), 0px 0px 2px rgba(0,0,0,.05), 0px 1px 4px rgba(0,0,0,.08);
.layout-topbar-logo {
display: flex;
align-items: center;
color: var(--surface-900);
font-size: 1.5rem;
font-weight: 500;
width: 300px;
border-radius: 12px;
img {
height: 2.5rem;
margin-right: .5rem;
}
&:focus {
@include focused();
}
}
.layout-topbar-button {
display: inline-flex;
justify-content: center;
align-items: center;
position: relative;
color: var(--text-color-secondary);
border-radius: 50%;
width: 3rem;
height: 3rem;
cursor: pointer;
transition: background-color $transitionDuration;
&:hover {
color: var(--text-color);
background-color: var(--surface-hover);
}
&:focus {
@include focused();
}
i {
font-size: 1.5rem;
}
span {
font-size: 1rem;
display: none;
}
}
.layout-menu-button {
margin-left: 2rem;
}
.layout-topbar-menu-button {
display: none;
i {
font-size: 1.25rem;
}
}
.layout-topbar-menu {
margin: 0 0 0 auto;
padding: 0;
list-style: none;
display: flex;
.layout-topbar-button {
margin-left: 1rem;
}
}
}
@media (max-width: 991px) {
.layout-topbar {
justify-content: space-between;
.layout-topbar-logo {
width: auto;
order: 2;
}
.layout-menu-button {
margin-left: 0;
order: 1;
}
.layout-topbar-menu-button {
display: inline-flex;
margin-left: 0;
order: 3;
}
.layout-topbar-menu {
margin-left: 0;
position: absolute;
flex-direction: column;
background-color: var(--surface-overlay);
box-shadow: 0px 3px 5px rgba(0,0,0,.02), 0px 0px 2px rgba(0,0,0,.05), 0px 1px 4px rgba(0,0,0,.08);
border-radius: 12px;
padding: 1rem;
right: 2rem;
top: 5rem;
min-width: 15rem;
display: none;
-webkit-animation: scalein 0.15s linear;
animation: scalein 0.15s linear;
&.layout-topbar-menu-mobile-active {
display: block
}
.layout-topbar-button {
margin-left: 0;
display: flex;
width: 100%;
height: auto;
justify-content: flex-start;
border-radius: 12px;
padding: 1rem;
i {
font-size: 1rem;
margin-right: .5rem;
}
span {
font-weight: medium;
display: block;
}
}
}
}
}

View File

@ -0,0 +1,63 @@
h1, h2, h3, h4, h5, h6 {
margin: 1.5rem 0 1rem 0;
font-family: inherit;
font-weight: 500;
line-height: 1.2;
color: var(--surface-900);
&:first-child {
margin-top: 0;
}
}
h1 {
font-size: 2.5rem;
}
h2 {
font-size: 2rem;
}
h3 {
font-size: 1.75rem;
}
h4 {
font-size: 1.5rem;
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
mark {
background: #FFF8E1;
padding: .25rem .4rem;
border-radius: $borderRadius;
font-family: monospace;
}
blockquote {
margin: 1rem 0;
padding: 0 2rem;
border-left: 4px solid #90A4AE;
}
hr {
border-top: solid var(--surface-border);
border-width: 1px 0 0 0;
margin: 1rem 0;
}
p {
margin: 0 0 1rem 0;
line-height: 1.5;
&:last-child {
margin-bottom: 0;
}
}

View File

@ -0,0 +1,20 @@
.card {
background: var(--surface-card);
border: 1px solid var(--surface-border);
padding: 2rem;
margin-bottom: 2rem;
box-shadow: var(--card-shadow);
border-radius: $borderRadius;
&:last-child {
margin-bottom: 0;
}
}
.p-toast {
&.p-toast-top-right,
&.p-toast-top-left,
&.p-toast-top-center {
top: 100px;
}
}

View File

@ -0,0 +1,4 @@
/* General */
$scale:14px; /* main font size */
$borderRadius:12px; /* border radius of layout element e.g. card, sidebar */
$transitionDuration:.2s; /* transition duration of layout elements e.g. sidebar, overlay menus */

View File

@ -0,0 +1,12 @@
@import './_variables';
@import "./_mixins";
@import "./_preloading";
@import "./_main";
@import "./_topbar";
@import "./_menu";
@import "./_config";
@import "./_content";
@import "./_footer";
@import "./_responsive";
@import "./_utils";
@import "./_typography";

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
import { HashSuffixPipe } from './hash-suffix.pipe';
describe('HashSuffixPipe', () => {
it('create an instance', () => {
const pipe = new HashSuffixPipe();
expect(pipe).toBeTruthy();
});
});

View File

@ -0,0 +1,33 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'hashSuffix'
})
export class HashSuffixPipe implements PipeTransform {
private static _this = new HashSuffixPipe();
public static transform(value: number): string {
return this._this.transform(value);
}
public transform(value: number): string {
if (value == null || value < 0) {
return '0';
}
const suffixes = [' H/s', ' KH/s', ' MH/s', ' GH/s', ' TH/s', ' PH/s', ' EH/s'];
let power = Math.floor(Math.log10(value) / 3);
if (power < 0) {
power = 0;
}
const scaledValue = value / Math.pow(1000, power);
const suffix = suffixes[power];
return scaledValue.toFixed(1) + suffix;
}
}

View File

@ -0,0 +1,36 @@
import { NgModule } from '@angular/core';
import { ButtonModule } from 'primeng/button';
import { ChartModule } from 'primeng/chart';
import { CheckboxModule } from 'primeng/checkbox';
import { DropdownModule } from 'primeng/dropdown';
import { FileUploadModule } from 'primeng/fileupload';
import { InputGroupModule } from 'primeng/inputgroup';
import { InputGroupAddonModule } from 'primeng/inputgroupaddon';
import { InputTextModule } from 'primeng/inputtext';
import { KnobModule } from 'primeng/knob';
import { SidebarModule } from 'primeng/sidebar';
import { SliderModule } from 'primeng/slider';
const primeNgModules = [
SidebarModule,
InputTextModule,
CheckboxModule,
DropdownModule,
SliderModule,
ButtonModule,
FileUploadModule,
KnobModule,
ChartModule,
InputGroupModule,
InputGroupAddonModule
];
@NgModule({
imports: [
...primeNgModules
],
exports: [
...primeNgModules
],
})
export class PrimeNGModule { }

View File

@ -26,9 +26,7 @@ export class SystemService {
current: 2237.5,
fanSpeed: 82,
temp: 60,
boardtemp1: 60,
boardtemp2: 60,
hashRate: 0,
hashRate: 475,
bestDiff: "0",
freeHeap: 200504,
coreVoltage: 1200,
@ -50,7 +48,10 @@ export class SystemService {
invertscreen: 0,
invertfanpolarity: 1,
autofanspeed: 1,
fanspeed: 100
fanspeed: 100,
boardtemp1: 30,
boardtemp2: 40
}
).pipe(delay(1000));
}

View File

@ -30,5 +30,5 @@ export interface ISystemInfo {
invertfanpolarity: number,
autofanspeed: number,
fanspeed: number,
coreVoltageActual: number
coreVoltageActual: number,
}

View File

@ -1,4 +1,5 @@
export enum eASICModel {
BM1366 = 'BM1366',
BM1368 = 'BM1368',
BM1397 = 'BM1397'
}
}

View File

@ -1,176 +1,20 @@
@import "app/layout/styles/layout/layout.scss";
@import "app/layout/styles/theme/vela-blue/theme.css";
@import "../node_modules/primeicons/primeicons.css";
@import "../node_modules/primeng/resources/primeng.min.css";
@import "../node_modules/primeflex/primeflex.min.css";
/* 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;
p-chart>div {
position: relative;
min-height: 40vh;
}
.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;
&.chart {
padding: 10px;
}
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;
}
}
}
button:disabled,
button[disabled] {
background: #45657f;
border: 1px solid #1c4567;
cursor: not-allowed;
&:hover {
background: #45657f;
border: 1px solid #1c4567;
}
}
input[type="text"],
input[type="password"],
input[type="number"],
select {
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

@ -45,6 +45,18 @@ void app_main(void)
.send_work_fn = BM1366_send_work};
GLOBAL_STATE.asic_job_frequency_ms = BM1366_FULLSCAN_MS;
GLOBAL_STATE.ASIC_functions = ASIC_functions;
} else if (strcmp(GLOBAL_STATE.asic_model, "BM1368") == 0) {
ESP_LOGI(TAG, "ASIC: BM1368");
AsicFunctions ASIC_functions = {.init_fn = BM1368_init,
.receive_result_fn = BM1368_proccess_work,
.set_max_baud_fn = BM1368_set_max_baud,
.set_difficulty_mask_fn = BM1368_set_job_difficulty_mask,
.send_work_fn = BM1368_send_work};
uint64_t bm1368_hashrate = GLOBAL_STATE.POWER_MANAGEMENT_MODULE.frequency_value * BM1368_CORE_COUNT * 1000000;
GLOBAL_STATE.asic_job_frequency_ms = ((double) NONCE_SPACE / (double) bm1368_hashrate) * 1000;
GLOBAL_STATE.ASIC_functions = ASIC_functions;
} else if (strcmp(GLOBAL_STATE.asic_model, "BM1397") == 0) {
ESP_LOGI(TAG, "ASIC: BM1397");

View File

@ -301,18 +301,16 @@ static double _calculate_network_difficulty(uint32_t nBits)
static void _check_for_best_diff(SystemModule * module, double diff, uint32_t nbits)
{
if (diff < module->best_nonce_diff) {
if (diff <= module->best_nonce_diff) {
return;
}
uint64_t old = module->best_nonce_diff;
module->best_nonce_diff = diff;
// only write to flash if new > old
if (module->best_nonce_diff > old) {
nvs_config_set_u64(NVS_CONFIG_BEST_DIFF, module->best_nonce_diff);
}
nvs_config_set_u64(NVS_CONFIG_BEST_DIFF, module->best_nonce_diff);
// make the best_nonce_diff into a string
_suffix_string((uint64_t) diff, module->best_diff_string, DIFF_STRING_SIZE, 0);
double network_diff = _calculate_network_difficulty(nbits);
if (diff > network_diff) {
module->FOUND_BLOCK = true;

View File

@ -43,13 +43,6 @@ void ASIC_result_task(void *pvParameters)
if (nonce_diff > GLOBAL_STATE->ASIC_TASK_MODULE.active_jobs[job_id]->pool_diff)
{
SYSTEM_notify_found_nonce(
&GLOBAL_STATE->SYSTEM_MODULE,
GLOBAL_STATE->ASIC_TASK_MODULE.active_jobs[job_id]->pool_diff,
nonce_diff,
GLOBAL_STATE->ASIC_TASK_MODULE.active_jobs[job_id]->target,
GLOBAL_STATE->POWER_MANAGEMENT_MODULE.power
);
STRATUM_V1_submit_share(
GLOBAL_STATE->sock,
@ -59,6 +52,10 @@ void ASIC_result_task(void *pvParameters)
GLOBAL_STATE->ASIC_TASK_MODULE.active_jobs[job_id]->ntime,
asic_result->nonce,
asic_result->rolled_version ^ GLOBAL_STATE->ASIC_TASK_MODULE.active_jobs[job_id]->version);
SYSTEM_notify_found_nonce(&GLOBAL_STATE->SYSTEM_MODULE, GLOBAL_STATE->ASIC_TASK_MODULE.active_jobs[job_id]->pool_diff,
nonce_diff, GLOBAL_STATE->ASIC_TASK_MODULE.active_jobs[job_id]->target,
GLOBAL_STATE->POWER_MANAGEMENT_MODULE.power);
}
}
}

View File

@ -177,7 +177,7 @@ void POWER_MANAGEMENT_task(void * pvParameters)
last_frequency_increase++;
}
}
} else if (strcmp(GLOBAL_STATE->asic_model, "BM1366") == 0) {
} else if (strcmp(GLOBAL_STATE->asic_model, "BM1366") == 0 || strcmp(GLOBAL_STATE->asic_model, "BM1368") == 0) {
power_management->chip_temp = EMC2101_get_internal_temp() + 5;
if (power_management->chip_temp > THROTTLE_TEMP &&

0
merge_bin_with_config.sh Normal file → Executable file
View File

View File

@ -30,13 +30,25 @@ Starting with v2.0.0, the ESP-Miner firmware requires some basic manufacturing d
The following are recommendations but it is necessary that you do have all values in your `config.cvs`file to flash properly.
- recommended values for the Bitaxe 1368 (supra)
```
key,type,encoding,value
main,namespace,,
asicfrequency,data,u16,490
asicvoltage,data,u16,1200
asicmodel,data,string,BM1368
devicemodel,data,string,supra
boardversion,data,string,400
```
- recommended values for the Bitaxe 1366 (ultra)
```
key,type,encoding,value
main,namespace,,
asicfrequency,data,u16,485
asicvoltage,data,u16,1320
asicvoltage,data,u16,1200
asicmodel,data,string,BM1366
devicemodel,data,string,ultra
boardversion,data,string,0.11