Initial import: ESP32 ZiFi32 firmware with SMB2/3 VFS bridge
Based on https://github.com/tslabs/zx-evo/tree/master/pentevo/esp32, extended with an SMB2/3 server that exposes the ZX Evolution's real SD card as a network share (browse/download/upload/delete), plus assorted stability and protocol-correctness fixes in vfs_bridge.cpp and the vendored libsmb2 - see firmware/README.md for the detailed changelog and current status. Also adds: - firmware/: ready-to-flash compiled binaries + flashing instructions - hardware/: adapter board connection diagram, PCB photo, and Gerber files - src/third_party/libsmb2: vendored from sahlberg/libsmb2 (LGPL-2.1, see its own COPYING/LICENCE files), de-submoduled to a plain snapshot with local patches applied directly Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
file(GLOB SOURCES "*.c" "*.cpp")
|
||||
|
||||
idf_component_register(SRCS ${SOURCES} INCLUDE_DIRS ".")
|
||||
|
||||
idf_build_set_property(COMPILE_OPTIONS "-ggdb;-g3" APPEND)
|
||||
idf_build_set_property(COMPILE_OPTIONS "-Wno-missing-field-initializers" APPEND)
|
||||
idf_build_set_property(COMPILE_OPTIONS "-Wno-implicit-fallthrough" APPEND)
|
||||
|
||||
# target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-error=format)
|
||||
# target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-error)
|
||||
# target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-but-set-variable)
|
||||
# target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-function)
|
||||
@@ -0,0 +1,314 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_tls.h"
|
||||
#include "esp_crt_bundle.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "at_uart.h"
|
||||
#include "at_cip.h"
|
||||
#include "wifi.h"
|
||||
|
||||
static const char *TAG = "at_cip";
|
||||
|
||||
static int cip_sock = -1;
|
||||
static int cip_type = CIP_TYPE_NONE;
|
||||
static char cip_host[64];
|
||||
static int cip_port = 0;
|
||||
static esp_tls_t *cip_tls = NULL;
|
||||
|
||||
void at_cip_init()
|
||||
{
|
||||
cip_sock = -1;
|
||||
cip_type = CIP_TYPE_NONE;
|
||||
cip_host[0] = 0;
|
||||
cip_port = 0;
|
||||
cip_tls = NULL;
|
||||
}
|
||||
|
||||
bool at_cip_is_connected()
|
||||
{
|
||||
return cip_sock >= 0 || cip_tls != NULL;
|
||||
}
|
||||
|
||||
void at_cip_status_line(char *buf, size_t buflen)
|
||||
{
|
||||
if (!at_cip_is_connected())
|
||||
{
|
||||
snprintf(buf, buflen, "STATUS:0");
|
||||
return;
|
||||
}
|
||||
|
||||
const char *type_str = cip_type == CIP_TYPE_UDP ? "UDP" : cip_type == CIP_TYPE_SSL ? "SSL" : "TCP";
|
||||
snprintf(buf, buflen, "STATUS:3\r\n+CIPSTATUS:0,\"%s\",\"%s\",%d,0,0", type_str, cip_host, cip_port);
|
||||
}
|
||||
|
||||
// Resolves and connects a raw socket via the plain BSD gethostbyname()/connect()
|
||||
// path. Shared by plain TCP and (as of the SSL support below) also by the TLS
|
||||
// path: esp_tls's own built-in connect step goes through getaddrinfo() instead,
|
||||
// which crashes (LoadProhibited, NULL FreeRTOS queue handle deep inside lwIP's
|
||||
// tcpip_task message-passing - confirmed via backtrace against esp-spi.elf,
|
||||
// root cause not fully understood) the first time it's exercised from this
|
||||
// task. gethostbyname() uses a different, older lwIP DNS code path that has
|
||||
// always worked fine here, so SSL connects through it too and only hands
|
||||
// esp_tls an already-open socket, skipping esp_tls's DNS/connect step entirely.
|
||||
static int at_cip_connect_raw_tcp(int sock_type, const char *host, int port, bool set_rcv_timeout)
|
||||
{
|
||||
// gethostbyname() (and, for the SSL caller, esp_tls's own getaddrinfo()
|
||||
// fallback below) both post through tcpip_task's mailbox - if this is the
|
||||
// very first network command since boot (no AT+CWJAP/AT+CWLAP yet), that
|
||||
// mailbox doesn't exist yet and the post crashes with a NULL-queue-handle
|
||||
// LoadProhibited (EXCVADDR=0x54, confirmed via console backtrace: reproduced
|
||||
// with a plain AT+CIPSTART="TCP" to a literal IP right after a fresh boot,
|
||||
// fixed by ensuring the WiFi stack is up first, same lazy-init used by
|
||||
// AT+CWLAP - safe to call again if already initialized).
|
||||
if (!net_mtx)
|
||||
initialize_wifi();
|
||||
|
||||
struct hostent *server = gethostbyname(host);
|
||||
if (!server)
|
||||
{
|
||||
ESP_LOGW(TAG, "DNS lookup failed for '%s'", host);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sock = socket(AF_INET, sock_type, 0);
|
||||
if (sock < 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "socket() failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
memcpy(&addr.sin_addr.s_addr, server->h_addr, server->h_length);
|
||||
|
||||
// A short receive timeout lets at_cip_rx_task poll periodically instead of
|
||||
// blocking forever, so it notices at_cip_close() promptly. Skipped here for
|
||||
// the SSL caller: applying it before the TLS handshake made mbedtls's own
|
||||
// blocking reads see spurious timeouts mid-handshake (a partial/timed-out
|
||||
// read on a socket mbedtls expects to block on desyncs its record parser -
|
||||
// confirmed by this exact change turning consistent MBEDTLS_ERR_SSL_INVALID_RECORD
|
||||
// handshake failures into a clean handshake). The SSL path sets this itself
|
||||
// after the handshake completes, once the socket moves to steady-state reads.
|
||||
if (set_rcv_timeout)
|
||||
{
|
||||
struct timeval tv = { 0, 200 * 1000 };
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
}
|
||||
|
||||
if (sock_type == SOCK_DGRAM)
|
||||
return sock; // UDP: no connect() handshake to wait on
|
||||
|
||||
// Bound the connect() itself with an explicit timeout via a non-blocking
|
||||
// connect + select(): an unreachable-but-not-immediately-rejected address
|
||||
// (e.g. a black-holed IP) can otherwise leave the OS/lwip TCP SYN retry
|
||||
// logic blocking this call for a minute or more, freezing the whole AT
|
||||
// command interface for that entire time.
|
||||
int flags = fcntl(sock, F_GETFL, 0);
|
||||
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
int rc = connect(sock, (struct sockaddr*)&addr, sizeof(addr));
|
||||
|
||||
if (rc < 0 && errno != EINPROGRESS)
|
||||
{
|
||||
ESP_LOGW(TAG, "connect() to %s:%d failed immediately", host, port);
|
||||
close(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rc < 0) // EINPROGRESS - wait for it to complete
|
||||
{
|
||||
fd_set wfds;
|
||||
FD_ZERO(&wfds);
|
||||
FD_SET(sock, &wfds);
|
||||
struct timeval conn_tv = { 10, 0 }; // 10s connect timeout
|
||||
|
||||
int sel = select(sock + 1, NULL, &wfds, NULL, &conn_tv);
|
||||
|
||||
if (sel <= 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "connect() to %s:%d timed out", host, port);
|
||||
close(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int so_err = 0;
|
||||
socklen_t so_len = sizeof(so_err);
|
||||
getsockopt(sock, SOL_SOCKET, SO_ERROR, &so_err, &so_len);
|
||||
|
||||
if (so_err != 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "connect() to %s:%d failed: errno %d", host, port, so_err);
|
||||
close(sock);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
fcntl(sock, F_SETFL, flags); // back to blocking for the (rare) case send() would need it
|
||||
return sock;
|
||||
}
|
||||
|
||||
static int at_cip_start_ssl(const char *host, int port)
|
||||
{
|
||||
int sock = at_cip_connect_raw_tcp(SOCK_STREAM, host, port, false);
|
||||
if (sock < 0)
|
||||
return -1;
|
||||
|
||||
// Kept local until the handshake fully succeeds below, deliberately not
|
||||
// written to the global cip_tls yet: at_cip_is_connected() (checked by
|
||||
// at_cip_rx_task's poll loop, running concurrently in its own task) goes by
|
||||
// cip_tls != NULL, and that task calling esp_tls_conn_read() on a context
|
||||
// that's mid-handshake raced with esp_tls_conn_new_sync() below over the
|
||||
// same mbedtls ssl_context - confirmed via mbedtls debug log showing
|
||||
// interleaved flush-output calls and a corrupted ssl->out_left, manifesting
|
||||
// as MBEDTLS_ERR_SSL_INTERNAL_ERROR then MBEDTLS_ERR_SSL_INVALID_RECORD.
|
||||
// The plain-TCP path never had this problem because it keeps its socket in
|
||||
// a local variable the same way, only publishing to cip_sock at the very end.
|
||||
esp_tls_t *tls = esp_tls_init();
|
||||
if (!tls)
|
||||
{
|
||||
ESP_LOGW(TAG, "esp_tls_init failed");
|
||||
close(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
esp_tls_set_conn_sockfd(tls, sock);
|
||||
esp_tls_set_conn_state(tls, ESP_TLS_CONNECTING); // skip esp_tls's own DNS/connect step - see at_cip_connect_raw_tcp()
|
||||
|
||||
esp_tls_cfg_t cfg = {};
|
||||
cfg.crt_bundle_attach = esp_crt_bundle_attach;
|
||||
cfg.timeout_ms = 10000; // matches the connect timeout already spent above
|
||||
|
||||
int rc = esp_tls_conn_new_sync(host, strlen(host), port, &cfg, tls);
|
||||
if (rc != 1)
|
||||
{
|
||||
ESP_LOGW(TAG, "TLS handshake with %s:%d failed", host, port);
|
||||
esp_tls_conn_destroy(tls); // also closes sock, it now owns the fd
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Now that the handshake is done, throttle reads the same way the plain-TCP
|
||||
// path does, so at_cip_rx_task's poll loop notices at_cip_close() promptly.
|
||||
struct timeval tv = { 0, 200 * 1000 };
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
|
||||
cip_tls = tls; // now safe to publish: handshake is done, at_cip_rx_task may use it
|
||||
cip_type = CIP_TYPE_SSL;
|
||||
strlcpy(cip_host, host, sizeof(cip_host));
|
||||
cip_port = port;
|
||||
|
||||
ESP_LOGI(TAG, "connected SSL %s:%d", host, port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int at_cip_start(const char *type, const char *host, int port)
|
||||
{
|
||||
if (at_cip_is_connected())
|
||||
return -2; // already connected
|
||||
|
||||
if (!strcasecmp(type, "SSL"))
|
||||
return at_cip_start_ssl(host, port);
|
||||
|
||||
int sock_type = 0;
|
||||
|
||||
if (!strcasecmp(type, "TCP"))
|
||||
sock_type = SOCK_STREAM;
|
||||
else if (!strcasecmp(type, "UDP"))
|
||||
sock_type = SOCK_DGRAM;
|
||||
else
|
||||
{
|
||||
ESP_LOGW(TAG, "unknown type '%s'", type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sock = at_cip_connect_raw_tcp(sock_type, host, port, true);
|
||||
if (sock < 0)
|
||||
return -1;
|
||||
|
||||
cip_sock = sock;
|
||||
cip_type = (sock_type == SOCK_DGRAM) ? CIP_TYPE_UDP : CIP_TYPE_TCP;
|
||||
strlcpy(cip_host, host, sizeof(cip_host));
|
||||
cip_port = port;
|
||||
|
||||
ESP_LOGI(TAG, "connected %s %s:%d", cip_type == CIP_TYPE_UDP ? "UDP" : "TCP", host, port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int at_cip_send(const uint8_t *data, size_t len)
|
||||
{
|
||||
if (cip_tls) return (int)esp_tls_conn_write(cip_tls, data, len);
|
||||
if (cip_sock < 0) return -1;
|
||||
return send(cip_sock, data, len, 0);
|
||||
}
|
||||
|
||||
void at_cip_close()
|
||||
{
|
||||
if (!at_cip_is_connected()) return;
|
||||
|
||||
if (cip_tls)
|
||||
{
|
||||
esp_tls_conn_destroy(cip_tls);
|
||||
cip_tls = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
close(cip_sock);
|
||||
cip_sock = -1;
|
||||
}
|
||||
|
||||
cip_type = CIP_TYPE_NONE;
|
||||
cip_host[0] = 0;
|
||||
cip_port = 0;
|
||||
}
|
||||
|
||||
void at_cip_rx_task(void *arg)
|
||||
{
|
||||
uint8_t buf[1024];
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (!at_cip_is_connected())
|
||||
{
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
continue;
|
||||
}
|
||||
|
||||
esp_tls_t *tls = cip_tls; // snapshot; at_cip_close() may change these concurrently
|
||||
int sock = cip_sock;
|
||||
int n = tls ? (int)esp_tls_conn_read(tls, buf, sizeof(buf)) : recv(sock, buf, sizeof(buf), 0);
|
||||
|
||||
if (n > 0)
|
||||
{
|
||||
char hdr[24];
|
||||
snprintf(hdr, sizeof(hdr), "\r\n+IPD,%d:", n);
|
||||
at_send_str(hdr);
|
||||
at_send_raw(buf, n);
|
||||
}
|
||||
else if (n == 0)
|
||||
{
|
||||
// Peer closed (TCP/SSL only - UDP recv() doesn't return 0 on a connected socket).
|
||||
at_cip_close();
|
||||
at_send_str("\r\nCLOSED\r\n");
|
||||
}
|
||||
// n < 0: either the SO_RCVTIMEO timeout expired (EAGAIN/EWOULDBLOCK, expected -
|
||||
// esp_tls_conn_read() surfaces the same underlying-socket timeout as a generic
|
||||
// negative return too, not distinguished from a real error here, matching how
|
||||
// the plain-TCP path already treats every negative read as "retry") or the
|
||||
// connection was closed out from under us by at_cip_close() (also fine,
|
||||
// at_cip_is_connected() will be false at the top next time).
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
enum
|
||||
{
|
||||
CIP_TYPE_NONE = 0,
|
||||
CIP_TYPE_TCP,
|
||||
CIP_TYPE_UDP,
|
||||
CIP_TYPE_SSL,
|
||||
};
|
||||
|
||||
void at_cip_init();
|
||||
int at_cip_start(const char *type, const char *host, int port); // 0 ok, <0 on error, -2 if already connected
|
||||
int at_cip_send(const uint8_t *data, size_t len); // returns bytes sent, or <0
|
||||
void at_cip_close();
|
||||
bool at_cip_is_connected();
|
||||
void at_cip_status_line(char *buf, size_t buflen);
|
||||
|
||||
void at_cip_rx_task(void *arg);
|
||||
@@ -0,0 +1,572 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_wifi.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "esp_spi_defs.h"
|
||||
#include "wifi.h"
|
||||
#include "at_cip.h"
|
||||
#include "at_uart.h"
|
||||
|
||||
#define AT_CIPSEND_MAX_LEN 1460
|
||||
|
||||
static const char *TAG = "at_uart";
|
||||
|
||||
static bool at_echo = true;
|
||||
|
||||
enum
|
||||
{
|
||||
AT_MODE_LINE,
|
||||
AT_MODE_CIPSEND_DATA,
|
||||
};
|
||||
|
||||
static int at_mode = AT_MODE_LINE;
|
||||
static uint8_t at_cipsend_buf[AT_CIPSEND_MAX_LEN];
|
||||
static size_t at_cipsend_len = 0;
|
||||
static size_t at_cipsend_have = 0;
|
||||
|
||||
void at_uart_init()
|
||||
{
|
||||
uart_config_t cfg = {};
|
||||
cfg.baud_rate = AT_UART_BAUD;
|
||||
cfg.data_bits = UART_DATA_8_BITS;
|
||||
cfg.parity = UART_PARITY_DISABLE;
|
||||
cfg.stop_bits = UART_STOP_BITS_1;
|
||||
cfg.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||
cfg.source_clk = UART_SCLK_DEFAULT;
|
||||
|
||||
ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1, AT_UART_RX_BUF_SIZE, AT_UART_TX_BUF_SIZE, 0, NULL, 0));
|
||||
ESP_ERROR_CHECK(uart_param_config(UART_NUM_1, &cfg));
|
||||
ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, AT_UART_TX_PIN, AT_UART_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
|
||||
|
||||
ESP_LOGI(TAG, "AT UART ready: TX=%d RX=%d %d 8N1", AT_UART_TX_PIN, AT_UART_RX_PIN, AT_UART_BAUD);
|
||||
|
||||
// Real ESP8266 AT firmware emits this on the AT UART itself after boot/AT+RST;
|
||||
// AT client libraries commonly block waiting for it before sending further commands.
|
||||
at_send_raw("\r\nready\r\n", 9);
|
||||
}
|
||||
|
||||
void at_send_raw(const void *data, size_t len)
|
||||
{
|
||||
uart_write_bytes(UART_NUM_1, (const char*)data, len);
|
||||
}
|
||||
|
||||
void at_send_str(const char *s)
|
||||
{
|
||||
at_send_raw(s, strlen(s));
|
||||
}
|
||||
|
||||
static void at_reply_ok()
|
||||
{
|
||||
at_send_str("\r\nOK\r\n");
|
||||
}
|
||||
|
||||
static void at_reply_error()
|
||||
{
|
||||
at_send_str("\r\nERROR\r\n");
|
||||
}
|
||||
|
||||
static void at_cmd_gmr()
|
||||
{
|
||||
// The ZX Evolution client's single-shot display capture after AT+GMR is
|
||||
// timing-sensitive on real hardware (verified: even small changes in send
|
||||
// timing shift what fraction of this reply the client's display happens to
|
||||
// catch, sometimes none of it, without affecting correctness - GMR's actual
|
||||
// reply content and the OK are always sent and processed correctly
|
||||
// regardless). A short delay between the echoed command line and the reply
|
||||
// gives the client's display time to get ready before the content arrives.
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
|
||||
// No leading CRLF and no blank line before OK: verified byte-for-byte
|
||||
// against a real ESP8266 AT module's actual AT+GMR reply on real hardware.
|
||||
char buf[192];
|
||||
at_send_str(CP_STRING);
|
||||
at_send_str("\r\n");
|
||||
|
||||
at_send_str(API_STRING);
|
||||
at_send_str("\r\n");
|
||||
|
||||
snprintf(buf, sizeof(buf), "SDK version:%s", IDF_VER);
|
||||
at_send_str(buf);
|
||||
at_send_str("\r\n");
|
||||
|
||||
at_send_str("OK");
|
||||
at_send_str("\r\n");
|
||||
}
|
||||
|
||||
// Splits comma-separated args in place, honoring double-quoted tokens
|
||||
// (e.g. `"ssid","pass"` or `"TCP",host,port`). Returns the number of tokens
|
||||
// found (up to max_args); each out[i] points into 'args' (NUL-terminated).
|
||||
static int at_parse_args(char *args, char *out[], int max_args)
|
||||
{
|
||||
int count = 0;
|
||||
char *p = args;
|
||||
|
||||
while (*p && count < max_args)
|
||||
{
|
||||
while (*p == ' ') p++;
|
||||
if (!*p) break;
|
||||
|
||||
if (*p == '"')
|
||||
{
|
||||
p++;
|
||||
out[count++] = p;
|
||||
while (*p && *p != '"') p++;
|
||||
if (*p == '"') *p++ = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
out[count++] = p;
|
||||
while (*p && *p != ',') p++;
|
||||
}
|
||||
|
||||
while (*p == ' ') p++;
|
||||
if (*p == ',') { *p = 0; p++; }
|
||||
else if (*p) *p = 0;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void at_cmd_cwmode(char *args, bool is_query)
|
||||
{
|
||||
if (is_query)
|
||||
{
|
||||
char buf[24];
|
||||
snprintf(buf, sizeof(buf), "\r\n+CWMODE:1\r\n");
|
||||
at_send_str(buf);
|
||||
at_reply_ok();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args || strcmp(args, "1"))
|
||||
{
|
||||
at_reply_error(); // only STA mode (1) is supported - AP/APSTA out of scope
|
||||
return;
|
||||
}
|
||||
|
||||
at_reply_ok();
|
||||
}
|
||||
|
||||
static void at_cmd_cwjap(char *args)
|
||||
{
|
||||
char *tok[2];
|
||||
|
||||
if (!args || at_parse_args(args, tok, 2) != 2)
|
||||
{
|
||||
at_reply_error();
|
||||
return;
|
||||
}
|
||||
|
||||
bool connected = wifi_connect_locked(tok[0], tok[1], CONNECT_TIMEOUT_MS);
|
||||
|
||||
if (!connected)
|
||||
{
|
||||
at_reply_error();
|
||||
return;
|
||||
}
|
||||
|
||||
// The ZX Evolution client (zifi_orig.asm) sends every AT command via zifi_send,
|
||||
// which does a throwaway read (zifi_check_receive_command) right after writing
|
||||
// the command - it patiently polls (screen-frame-gated, ~5s worst case) until
|
||||
// ANY byte shows up, then slurps and discards it. For AT+CWJAP specifically,
|
||||
// a SEPARATE loop right after re-polls for "OK\r" with no timeout at all. Real
|
||||
// ESP8266 hardware takes several seconds to actually join, so that throwaway
|
||||
// read naturally times out empty before the real reply lands, leaving it for
|
||||
// the real wait loop to catch. Our join is near-instant (verified: the discard
|
||||
// read grabs our whole reply within milliseconds, then the real wait loop hangs
|
||||
// forever with nothing left to find) - so replying quickly here deterministically
|
||||
// breaks this client. Deliberately hold the reply past that ~5s throwaway window.
|
||||
vTaskDelay(pdMS_TO_TICKS(6500));
|
||||
at_reply_ok();
|
||||
}
|
||||
|
||||
static void at_cmd_cwqap()
|
||||
{
|
||||
wifi_disconnect_now_locked();
|
||||
at_reply_ok();
|
||||
}
|
||||
|
||||
static void at_cmd_cwlap()
|
||||
{
|
||||
if (!net_mtx)
|
||||
initialize_wifi(); // lazily creates net_mtx as its first action
|
||||
|
||||
if (xSemaphoreTake(net_mtx, portMAX_DELAY) != pdTRUE)
|
||||
{
|
||||
at_reply_error();
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t err = (esp_err_t)wf_scan(300);
|
||||
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
xSemaphoreGive(net_mtx);
|
||||
at_reply_error();
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t num = wf_get_ap_num();
|
||||
char line[160];
|
||||
|
||||
for (uint16_t i = 0; i < num; i++)
|
||||
{
|
||||
uint8_t auth, chan;
|
||||
int8_t rssi;
|
||||
uint8_t *ssid;
|
||||
wf_get_ap(i, auth, rssi, chan, ssid);
|
||||
snprintf(line, sizeof(line), "\r\n+CWLAP:(%u,\"%s\",%d,%u)", auth, ssid, rssi, chan);
|
||||
at_send_str(line);
|
||||
}
|
||||
|
||||
xSemaphoreGive(net_mtx);
|
||||
at_reply_ok();
|
||||
}
|
||||
|
||||
static void at_cmd_cipsntpcfg(char *args, bool is_query)
|
||||
{
|
||||
bool enable;
|
||||
int timezone;
|
||||
char s1[64], s2[64], s3[64];
|
||||
|
||||
if (is_query)
|
||||
{
|
||||
sntp_get_config(enable, timezone, s1, s2, s3, sizeof(s1));
|
||||
|
||||
char buf[220];
|
||||
if (s3[0])
|
||||
snprintf(buf, sizeof(buf), "\r\n+CIPSNTPCFG:%d,%d,\"%s\",\"%s\",\"%s\"\r\n", enable ? 1 : 0, timezone, s1, s2, s3);
|
||||
else if (s2[0])
|
||||
snprintf(buf, sizeof(buf), "\r\n+CIPSNTPCFG:%d,%d,\"%s\",\"%s\"\r\n", enable ? 1 : 0, timezone, s1, s2);
|
||||
else
|
||||
snprintf(buf, sizeof(buf), "\r\n+CIPSNTPCFG:%d,%d,\"%s\"\r\n", enable ? 1 : 0, timezone, s1);
|
||||
at_send_str(buf);
|
||||
at_reply_ok();
|
||||
return;
|
||||
}
|
||||
|
||||
char *tok[5];
|
||||
int n = args ? at_parse_args(args, tok, 5) : 0;
|
||||
|
||||
if (n < 2)
|
||||
{
|
||||
at_reply_error();
|
||||
return;
|
||||
}
|
||||
|
||||
char *endp = NULL;
|
||||
long en = strtol(tok[0], &endp, 10);
|
||||
if (!endp || *endp || (en != 0 && en != 1))
|
||||
{
|
||||
at_reply_error();
|
||||
return;
|
||||
}
|
||||
|
||||
endp = NULL;
|
||||
long tz = strtol(tok[1], &endp, 10);
|
||||
if (!endp || *endp || tz < -12 || tz > 14)
|
||||
{
|
||||
at_reply_error();
|
||||
return;
|
||||
}
|
||||
|
||||
sntp_apply_config(en != 0, (int)tz, n >= 3 ? tok[2] : NULL, n >= 4 ? tok[3] : NULL, n >= 5 ? tok[4] : NULL);
|
||||
at_reply_ok();
|
||||
}
|
||||
|
||||
static void at_cmd_cipsntptime()
|
||||
{
|
||||
char timestr[40];
|
||||
sntp_format_time(timestr, sizeof(timestr));
|
||||
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "\r\n+CIPSNTPTIME:%s\r\n", timestr);
|
||||
at_send_str(buf);
|
||||
at_reply_ok();
|
||||
}
|
||||
|
||||
static void at_cmd_cifsr()
|
||||
{
|
||||
uint8_t ip[4], mask[4], gate[4], mac[6] = {0};
|
||||
get_ip(ip, mask, gate);
|
||||
esp_wifi_get_mac(WIFI_IF_STA, mac);
|
||||
|
||||
char buf[96];
|
||||
snprintf(buf, sizeof(buf), "\r\n+CIFSR:STAIP,\"%u.%u.%u.%u\"\r\n+CIFSR:STAMAC,\"%02x:%02x:%02x:%02x:%02x:%02x\"\r\n",
|
||||
ip[0], ip[1], ip[2], ip[3], mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
at_send_str(buf);
|
||||
at_reply_ok();
|
||||
}
|
||||
|
||||
static void at_cmd_cipstatus()
|
||||
{
|
||||
char buf[128];
|
||||
at_cip_status_line(buf, sizeof(buf));
|
||||
at_send_str("\r\n");
|
||||
at_send_str(buf);
|
||||
at_send_str("\r\n");
|
||||
at_reply_ok();
|
||||
}
|
||||
|
||||
static void at_cmd_cipstart(char *args)
|
||||
{
|
||||
char *tok[3];
|
||||
|
||||
if (!args || at_parse_args(args, tok, 3) != 3)
|
||||
{
|
||||
at_reply_error();
|
||||
return;
|
||||
}
|
||||
|
||||
int port = atoi(tok[2]);
|
||||
int rc = at_cip_start(tok[0], tok[1], port);
|
||||
|
||||
if (rc == -2)
|
||||
{
|
||||
at_send_str("\r\nALREADY CONNECTED\r\n");
|
||||
at_reply_error();
|
||||
return;
|
||||
}
|
||||
|
||||
if (rc != 0)
|
||||
{
|
||||
at_reply_error();
|
||||
return;
|
||||
}
|
||||
|
||||
at_send_str("\r\nCONNECT\r\n");
|
||||
at_reply_ok();
|
||||
}
|
||||
|
||||
static void at_cmd_cipsend(char *args)
|
||||
{
|
||||
if (!args)
|
||||
{
|
||||
at_reply_error();
|
||||
return;
|
||||
}
|
||||
|
||||
int len = atoi(args);
|
||||
|
||||
if (len <= 0 || len > AT_CIPSEND_MAX_LEN)
|
||||
{
|
||||
at_reply_error();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!at_cip_is_connected())
|
||||
{
|
||||
at_reply_error();
|
||||
return;
|
||||
}
|
||||
|
||||
at_cipsend_len = (size_t)len;
|
||||
at_cipsend_have = 0;
|
||||
at_mode = AT_MODE_CIPSEND_DATA;
|
||||
|
||||
at_send_str("\r\nOK\r\n>");
|
||||
}
|
||||
|
||||
static void at_cmd_cipclose()
|
||||
{
|
||||
at_cip_close();
|
||||
at_send_str("\r\nCLOSED\r\n");
|
||||
at_reply_ok();
|
||||
}
|
||||
|
||||
// Dispatches one complete, NUL-terminated line (trailing \r/\n already stripped by caller).
|
||||
static void at_dispatch_line(char *line, size_t len)
|
||||
{
|
||||
if (at_echo)
|
||||
{
|
||||
at_send_str(line);
|
||||
at_send_str("\r\n");
|
||||
}
|
||||
|
||||
if (len == 0) return; // silently ignore stray blank lines, matches real AT modules
|
||||
|
||||
if (!strcasecmp(line, "AT"))
|
||||
{
|
||||
at_reply_ok();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strncasecmp(line, "ATE", 3) && len == 4 && (line[3] == '0' || line[3] == '1'))
|
||||
{
|
||||
at_echo = (line[3] == '1');
|
||||
at_reply_ok();
|
||||
return;
|
||||
}
|
||||
|
||||
if (strncasecmp(line, "AT+", 3) != 0)
|
||||
{
|
||||
at_reply_error();
|
||||
return;
|
||||
}
|
||||
|
||||
char *cmd = line + 3;
|
||||
char *eq = strchr(cmd, '=');
|
||||
char *args = NULL;
|
||||
bool is_query = false;
|
||||
|
||||
if (eq)
|
||||
{
|
||||
*eq = 0;
|
||||
args = eq + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t clen = strlen(cmd);
|
||||
if (clen && cmd[clen - 1] == '?')
|
||||
{
|
||||
cmd[clen - 1] = 0;
|
||||
is_query = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!strcasecmp(cmd, "RST"))
|
||||
{
|
||||
at_reply_ok();
|
||||
ESP_LOGE(TAG, "software restart requested by AT+RST on UART1");
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
esp_restart();
|
||||
}
|
||||
else if (!strcasecmp(cmd, "GMR"))
|
||||
at_cmd_gmr();
|
||||
else if (!strcasecmp(cmd, "CWMODE") || !strcasecmp(cmd, "CWMODE_CUR") || !strcasecmp(cmd, "CWMODE_DEF"))
|
||||
at_cmd_cwmode(args, is_query);
|
||||
else if (!strcasecmp(cmd, "CWJAP") || !strcasecmp(cmd, "CWJAP_CUR") || !strcasecmp(cmd, "CWJAP_DEF"))
|
||||
at_cmd_cwjap(args);
|
||||
else if (!strcasecmp(cmd, "CWQAP"))
|
||||
at_cmd_cwqap();
|
||||
else if (!strcasecmp(cmd, "CWLAP"))
|
||||
at_cmd_cwlap();
|
||||
else if (!strcasecmp(cmd, "CWAUTOCONN"))
|
||||
at_reply_ok(); // autoconnect-on-boot persistence is out of scope by design; accept and no-op
|
||||
else if (!strcasecmp(cmd, "CIPMUX"))
|
||||
{
|
||||
// Only single-connection mode (0) is supported - CIPMUX multi-connection is out of scope.
|
||||
if (args && !strcmp(args, "0"))
|
||||
at_reply_ok();
|
||||
else
|
||||
at_reply_error();
|
||||
}
|
||||
else if (!strcasecmp(cmd, "CIFSR"))
|
||||
at_cmd_cifsr();
|
||||
else if (!strcasecmp(cmd, "CIPSNTPCFG"))
|
||||
at_cmd_cipsntpcfg(args, is_query);
|
||||
else if (!strcasecmp(cmd, "CIPSNTPTIME"))
|
||||
at_cmd_cipsntptime();
|
||||
else if (!strcasecmp(cmd, "CIPSTATUS"))
|
||||
at_cmd_cipstatus();
|
||||
else if (!strcasecmp(cmd, "CIPSTART"))
|
||||
at_cmd_cipstart(args);
|
||||
else if (!strcasecmp(cmd, "CIPSEND"))
|
||||
at_cmd_cipsend(args);
|
||||
else if (!strcasecmp(cmd, "CIPCLOSE"))
|
||||
at_cmd_cipclose();
|
||||
else
|
||||
{
|
||||
ESP_LOGW(TAG, "unknown command '%s'", cmd);
|
||||
at_reply_error();
|
||||
}
|
||||
}
|
||||
|
||||
static void at_process_cipsend_byte(uint8_t b)
|
||||
{
|
||||
at_cipsend_buf[at_cipsend_have++] = b;
|
||||
|
||||
if (at_cipsend_have < at_cipsend_len)
|
||||
return;
|
||||
|
||||
int sent = at_cip_send(at_cipsend_buf, at_cipsend_have);
|
||||
at_mode = AT_MODE_LINE;
|
||||
|
||||
if (sent < 0 || (size_t)sent != at_cipsend_have)
|
||||
at_send_str("\r\nSEND FAIL\r\n");
|
||||
else
|
||||
at_send_str("\r\nSEND OK\r\n");
|
||||
}
|
||||
|
||||
void at_task(void *arg)
|
||||
{
|
||||
char linebuf[AT_LINE_MAX_LEN];
|
||||
size_t linelen = 0;
|
||||
bool just_saw_cr = false;
|
||||
uint8_t rxbuf[64];
|
||||
|
||||
while (1)
|
||||
{
|
||||
int n = uart_read_bytes(UART_NUM_1, rxbuf, sizeof(rxbuf), pdMS_TO_TICKS(20));
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
uint8_t b = rxbuf[i];
|
||||
|
||||
// Swallow the LF half of a CRLF pair unconditionally, *before* looking at
|
||||
// at_mode - a command dispatched on '\r' (e.g. AT+CIPSEND=<len>) can switch
|
||||
// straight into AT_MODE_CIPSEND_DATA, and the following '\n' must not be
|
||||
// mistaken for the first byte of that payload.
|
||||
if (just_saw_cr)
|
||||
{
|
||||
just_saw_cr = false;
|
||||
if (b == '\n') continue;
|
||||
}
|
||||
|
||||
if (at_mode == AT_MODE_CIPSEND_DATA)
|
||||
{
|
||||
at_process_cipsend_byte(b);
|
||||
continue;
|
||||
}
|
||||
|
||||
char c = (char)b;
|
||||
|
||||
if (c == '\r')
|
||||
{
|
||||
linebuf[linelen] = 0;
|
||||
at_dispatch_line(linebuf, linelen);
|
||||
linelen = 0;
|
||||
just_saw_cr = true;
|
||||
}
|
||||
else if (c == '\n')
|
||||
{
|
||||
linebuf[linelen] = 0;
|
||||
at_dispatch_line(linebuf, linelen);
|
||||
linelen = 0;
|
||||
}
|
||||
else if (linelen < AT_LINE_MAX_LEN - 1)
|
||||
{
|
||||
linebuf[linelen++] = c;
|
||||
}
|
||||
else
|
||||
{
|
||||
linelen = 0;
|
||||
at_reply_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TaskHandle_t g_at_task_handle = NULL;
|
||||
TaskHandle_t g_at_cip_rx_task_handle = NULL;
|
||||
|
||||
void at_uart_suspend()
|
||||
{
|
||||
if (g_at_task_handle) vTaskSuspend(g_at_task_handle);
|
||||
if (g_at_cip_rx_task_handle) vTaskSuspend(g_at_cip_rx_task_handle);
|
||||
}
|
||||
|
||||
void at_uart_resume()
|
||||
{
|
||||
if (g_at_task_handle) vTaskResume(g_at_task_handle);
|
||||
if (g_at_cip_rx_task_handle) vTaskResume(g_at_cip_rx_task_handle);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#define AT_UART_TX_PIN 18
|
||||
#define AT_UART_RX_PIN 8
|
||||
#define AT_UART_BAUD 115200
|
||||
#define AT_UART_RX_BUF_SIZE 2048
|
||||
#define AT_UART_TX_BUF_SIZE 2048
|
||||
#define AT_LINE_MAX_LEN 512
|
||||
|
||||
void at_uart_init();
|
||||
void at_task(void *arg);
|
||||
void at_send_raw(const void *data, size_t len);
|
||||
void at_send_str(const char *s);
|
||||
|
||||
// Populated by main.cpp's xTaskCreatePinnedToCoreWithCaps() calls for
|
||||
// at_task/at_cip_rx_task - used by at_uart_suspend()/at_uart_resume() below.
|
||||
extern TaskHandle_t g_at_task_handle;
|
||||
extern TaskHandle_t g_at_cip_rx_task_handle;
|
||||
|
||||
// Pause/resume the whole AT-command modem interface (both the UART command
|
||||
// parser and the CIP data-mode RX relay) - used while the SMB2/3 server is
|
||||
// active, since the Z80 side is single-tasking and can't be driving both
|
||||
// the classic ZiFi AT interface and the smbvfs.wmf plugin at once anyway.
|
||||
void at_uart_suspend();
|
||||
void at_uart_resume();
|
||||
+10835
File diff suppressed because it is too large
Load Diff
+696
@@ -0,0 +1,696 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct CmdApp CmdApp;
|
||||
typedef struct CmdFsManager CmdFsManager;
|
||||
typedef struct CmdPanel CmdPanel;
|
||||
|
||||
void cmd_console_register_system_commands();
|
||||
void cmd_set_status(CmdApp *app, const char *fmt, ...);
|
||||
void cmd_show_error(CmdApp *app, const char *title, esp_err_t err);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
// --------------- cmd_types.h ---------------
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define CMD_TEXT_COLS 80
|
||||
#define CMD_TEXT_ROWS 30
|
||||
#define CMD_FILE_NAME_MAX 256
|
||||
#define CMD_PATH_MAX 256
|
||||
#define CMD_PANEL_COUNT 2
|
||||
|
||||
#ifndef CMD_ERR_BASE
|
||||
#define CMD_ERR_BASE 0x7000
|
||||
#endif
|
||||
|
||||
typedef esp_err_t CmdErr;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CMD_DEVICE_SD = 0,
|
||||
CMD_DEVICE_FAT,
|
||||
CMD_DEVICE_TSF,
|
||||
CMD_DEVICE_MAX,
|
||||
CMD_DEVICE_INVALID = -1
|
||||
} cmd_device_id_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CMD_ENTRY_UNKNOWN = 0,
|
||||
CMD_ENTRY_FILE,
|
||||
CMD_ENTRY_DIR,
|
||||
CMD_ENTRY_PARENT
|
||||
} cmd_entry_type_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CMD_ENTRY_FLAG_NONE = 0,
|
||||
CMD_ENTRY_FLAG_SELECTED = 1u << 0,
|
||||
CMD_ENTRY_FLAG_ERROR = 1u << 1
|
||||
} cmd_entry_flags_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char name[CMD_FILE_NAME_MAX];
|
||||
cmd_entry_type_t type;
|
||||
uint64_t size;
|
||||
uint64_t mtime;
|
||||
uint32_t flags;
|
||||
} cmd_file_entry_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
} cmd_point_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int w;
|
||||
int h;
|
||||
} cmd_size_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
int w;
|
||||
int h;
|
||||
} cmd_rect_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CMD_KEY_NONE = 0,
|
||||
CMD_KEY_CHAR,
|
||||
CMD_KEY_ESC,
|
||||
CMD_KEY_ENTER,
|
||||
CMD_KEY_BACKSPACE,
|
||||
CMD_KEY_TAB,
|
||||
CMD_KEY_UP,
|
||||
CMD_KEY_DOWN,
|
||||
CMD_KEY_LEFT,
|
||||
CMD_KEY_RIGHT,
|
||||
CMD_KEY_HOME,
|
||||
CMD_KEY_END,
|
||||
CMD_KEY_PAGE_UP,
|
||||
CMD_KEY_PAGE_DOWN,
|
||||
CMD_KEY_INSERT,
|
||||
CMD_KEY_DELETE,
|
||||
CMD_KEY_F1,
|
||||
CMD_KEY_F2,
|
||||
CMD_KEY_F3,
|
||||
CMD_KEY_F4,
|
||||
CMD_KEY_F5,
|
||||
CMD_KEY_F6,
|
||||
CMD_KEY_F7,
|
||||
CMD_KEY_F8,
|
||||
CMD_KEY_F9,
|
||||
CMD_KEY_F10,
|
||||
CMD_KEY_F11,
|
||||
CMD_KEY_F12,
|
||||
CMD_KEY_UNKNOWN
|
||||
} cmd_key_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CMD_EVENT_NONE = 0,
|
||||
CMD_EVENT_KEY,
|
||||
CMD_EVENT_QUIT
|
||||
} cmd_event_type_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
cmd_event_type_t type;
|
||||
cmd_key_t key;
|
||||
uint32_t ch;
|
||||
uint32_t mods;
|
||||
} cmd_event_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CMD_KEY_MOD_NONE = 0,
|
||||
CMD_KEY_MOD_SHIFT = 1u << 0,
|
||||
CMD_KEY_MOD_CTRL = 1u << 1,
|
||||
CMD_KEY_MOD_ALT = 1u << 2
|
||||
} cmd_key_mod_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CMD_OK = ESP_OK,
|
||||
CMD_ERR_INVALID_DEVICE = CMD_ERR_BASE,
|
||||
CMD_ERR_NOT_MOUNTED,
|
||||
CMD_ERR_PATH_TOO_LONG,
|
||||
CMD_ERR_ENTRY_TOO_LONG,
|
||||
CMD_ERR_DIR_FULL,
|
||||
CMD_ERR_INPUT_UNAVAILABLE,
|
||||
CMD_ERR_CANCELLED
|
||||
} cmd_err_t;
|
||||
|
||||
const char *cmd_device_name(cmd_device_id_t id);
|
||||
const char *cmd_entry_type_name(cmd_entry_type_t type);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
// --------------- cmd_display.h ---------------
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define CMD_DISPLAY_DEFAULT_COLS CMD_TEXT_COLS
|
||||
#define CMD_DISPLAY_DEFAULT_ROWS CMD_TEXT_ROWS
|
||||
#define CMD_DISPLAY_MAX_COLS 160
|
||||
#define CMD_DISPLAY_MAX_ROWS 64
|
||||
#define CMD_DISPLAY_COLS CMD_DISPLAY_MAX_COLS
|
||||
#define CMD_DISPLAY_ROWS CMD_DISPLAY_MAX_ROWS
|
||||
#define CMD_DISPLAY_CELL_COUNT (CMD_DISPLAY_COLS * CMD_DISPLAY_ROWS)
|
||||
#define CMD_DISPLAY_MUX_MAX_BACKENDS 4
|
||||
#define CMD_DISPLAY_BACKEND_FLAG_FORCE_PARTIAL 1u
|
||||
|
||||
typedef uint16_t cmd_display_attr_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CMD_DISPLAY_COLOR_BLACK = 0,
|
||||
CMD_DISPLAY_COLOR_BLUE,
|
||||
CMD_DISPLAY_COLOR_GREEN,
|
||||
CMD_DISPLAY_COLOR_CYAN,
|
||||
CMD_DISPLAY_COLOR_RED,
|
||||
CMD_DISPLAY_COLOR_MAGENTA,
|
||||
CMD_DISPLAY_COLOR_YELLOW,
|
||||
CMD_DISPLAY_COLOR_WHITE,
|
||||
CMD_DISPLAY_COLOR_BRIGHT_BLACK,
|
||||
CMD_DISPLAY_COLOR_BRIGHT_BLUE,
|
||||
CMD_DISPLAY_COLOR_BRIGHT_GREEN,
|
||||
CMD_DISPLAY_COLOR_BRIGHT_CYAN,
|
||||
CMD_DISPLAY_COLOR_BRIGHT_RED,
|
||||
CMD_DISPLAY_COLOR_BRIGHT_MAGENTA,
|
||||
CMD_DISPLAY_COLOR_BRIGHT_YELLOW,
|
||||
CMD_DISPLAY_COLOR_BRIGHT_WHITE
|
||||
} cmd_display_color_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CMD_DISPLAY_ATTR_FLAG_NONE = 0,
|
||||
CMD_DISPLAY_ATTR_FLAG_BOLD = 1u << 0,
|
||||
CMD_DISPLAY_ATTR_FLAG_INVERSE = 1u << 1,
|
||||
CMD_DISPLAY_ATTR_FLAG_UNDERLINE = 1u << 2,
|
||||
CMD_DISPLAY_ATTR_FLAG_SELECTED = 1u << 3
|
||||
} cmd_display_attr_flag_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CMD_DISPLAY_MODE_CONSOLE = 0,
|
||||
CMD_DISPLAY_MODE_FT812,
|
||||
CMD_DISPLAY_MODE_MIRROR
|
||||
} CmdDisplayMode;
|
||||
|
||||
#define CMD_DISPLAY_ATTR_FG_MASK 0x000f
|
||||
#define CMD_DISPLAY_ATTR_BG_MASK 0x00f0
|
||||
#define CMD_DISPLAY_ATTR_FLAGS_MASK 0xff00
|
||||
#define CMD_DISPLAY_ATTR_BG_SHIFT 4
|
||||
#define CMD_DISPLAY_ATTR_FLAGS_SHIFT 8
|
||||
#define CMD_DISPLAY_ATTR_DEFAULT ((cmd_display_attr_t)(CMD_DISPLAY_COLOR_WHITE | (CMD_DISPLAY_COLOR_BLACK << CMD_DISPLAY_ATTR_BG_SHIFT)))
|
||||
|
||||
#define CMD_DISPLAY_BOX_TL ((char)0xC9)
|
||||
#define CMD_DISPLAY_BOX_TR ((char)0xBB)
|
||||
#define CMD_DISPLAY_BOX_BL ((char)0xC8)
|
||||
#define CMD_DISPLAY_BOX_BR ((char)0xBC)
|
||||
#define CMD_DISPLAY_BOX_H ((char)0xCD)
|
||||
#define CMD_DISPLAY_BOX_V ((char)0xBA)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char ch;
|
||||
cmd_display_attr_t attr;
|
||||
} CmdDisplayCell;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
cmd_size_t size;
|
||||
CmdDisplayCell cells[CMD_DISPLAY_ROWS][CMD_DISPLAY_COLS];
|
||||
} CmdDisplayBuffer;
|
||||
|
||||
typedef struct CmdDisplayBackend CmdDisplayBackend;
|
||||
typedef struct CmdDisplayMux CmdDisplayMux;
|
||||
|
||||
typedef esp_err_t (*CmdDisplayBackendInitFn)(CmdDisplayBackend *backend);
|
||||
typedef void (*CmdDisplayBackendDeinitFn)(CmdDisplayBackend *backend);
|
||||
typedef esp_err_t (*CmdDisplayBackendClearFn)(CmdDisplayBackend *backend);
|
||||
typedef esp_err_t (*CmdDisplayBackendPresentFn)(CmdDisplayBackend *backend, const CmdDisplayBuffer *buffer);
|
||||
typedef esp_err_t (*CmdDisplayBackendPutCharFn)(CmdDisplayBackend *backend, int x, int y, char ch, cmd_display_attr_t attr);
|
||||
typedef esp_err_t (*CmdDisplayBackendWriteTextFn)(CmdDisplayBackend *backend, int x, int y, const char *text, cmd_display_attr_t attr);
|
||||
typedef esp_err_t (*CmdDisplayBackendGetSizeFn)(CmdDisplayBackend *backend, cmd_size_t *size);
|
||||
|
||||
struct CmdDisplayBackend
|
||||
{
|
||||
const char *name;
|
||||
void *ctx;
|
||||
uint32_t flags;
|
||||
|
||||
CmdDisplayBackendInitFn init;
|
||||
CmdDisplayBackendDeinitFn deinit;
|
||||
CmdDisplayBackendClearFn clear;
|
||||
CmdDisplayBackendPresentFn present;
|
||||
CmdDisplayBackendPutCharFn put_char;
|
||||
CmdDisplayBackendWriteTextFn write_text;
|
||||
CmdDisplayBackendGetSizeFn get_size;
|
||||
};
|
||||
|
||||
struct CmdDisplayMux
|
||||
{
|
||||
CmdDisplayBackend *backends[CMD_DISPLAY_MUX_MAX_BACKENDS];
|
||||
size_t count;
|
||||
};
|
||||
|
||||
extern CmdDisplayBackend cmd_display_console_backend;
|
||||
extern CmdDisplayBackend cmd_display_ft812_backend;
|
||||
|
||||
cmd_display_attr_t cmd_display_make_attr(cmd_display_color_t fg, cmd_display_color_t bg, uint8_t flags);
|
||||
cmd_display_color_t cmd_display_attr_fg(cmd_display_attr_t attr);
|
||||
cmd_display_color_t cmd_display_attr_bg(cmd_display_attr_t attr);
|
||||
uint8_t cmd_display_attr_flags(cmd_display_attr_t attr);
|
||||
|
||||
const char *cmd_display_mode_name(CmdDisplayMode mode);
|
||||
bool cmd_display_mode_parse(const char *text, CmdDisplayMode *mode);
|
||||
bool cmd_display_mode_has_console(CmdDisplayMode mode);
|
||||
bool cmd_display_mode_has_ft812(CmdDisplayMode mode);
|
||||
|
||||
void cmd_display_buffer_init(CmdDisplayBuffer *buffer);
|
||||
esp_err_t cmd_display_buffer_set_size(CmdDisplayBuffer *buffer, int w, int h);
|
||||
void cmd_display_buffer_clear(CmdDisplayBuffer *buffer, char ch, cmd_display_attr_t attr);
|
||||
esp_err_t cmd_display_buffer_put_char(CmdDisplayBuffer *buffer, int x, int y, char ch, cmd_display_attr_t attr);
|
||||
esp_err_t cmd_display_buffer_write_text(CmdDisplayBuffer *buffer, int x, int y, const char *text, cmd_display_attr_t attr);
|
||||
esp_err_t cmd_display_buffer_fill_rect(CmdDisplayBuffer *buffer, const cmd_rect_t *rect, char ch, cmd_display_attr_t attr);
|
||||
esp_err_t cmd_display_buffer_draw_hline(CmdDisplayBuffer *buffer, int x, int y, int w, char ch, cmd_display_attr_t attr);
|
||||
esp_err_t cmd_display_buffer_draw_vline(CmdDisplayBuffer *buffer, int x, int y, int h, char ch, cmd_display_attr_t attr);
|
||||
esp_err_t cmd_display_buffer_draw_box(CmdDisplayBuffer *buffer, const cmd_rect_t *rect, cmd_display_attr_t attr);
|
||||
CmdDisplayCell *cmd_display_buffer_cell(CmdDisplayBuffer *buffer, int x, int y);
|
||||
const CmdDisplayCell *cmd_display_buffer_cell_const(const CmdDisplayBuffer *buffer, int x, int y);
|
||||
bool cmd_display_point_inside(int x, int y);
|
||||
bool cmd_display_buffer_point_inside(const CmdDisplayBuffer *buffer, int x, int y);
|
||||
|
||||
esp_err_t cmd_display_backend_init(CmdDisplayBackend *backend);
|
||||
void cmd_display_backend_deinit(CmdDisplayBackend *backend);
|
||||
esp_err_t cmd_display_backend_clear(CmdDisplayBackend *backend);
|
||||
esp_err_t cmd_display_backend_present(CmdDisplayBackend *backend, const CmdDisplayBuffer *buffer);
|
||||
esp_err_t cmd_display_backend_put_char(CmdDisplayBackend *backend, int x, int y, char ch, cmd_display_attr_t attr);
|
||||
esp_err_t cmd_display_backend_write_text(CmdDisplayBackend *backend, int x, int y, const char *text, cmd_display_attr_t attr);
|
||||
esp_err_t cmd_display_backend_get_size(CmdDisplayBackend *backend, cmd_size_t *size);
|
||||
|
||||
void cmd_display_mux_init(CmdDisplayMux *mux);
|
||||
esp_err_t cmd_display_mux_add_backend(CmdDisplayMux *mux, CmdDisplayBackend *backend);
|
||||
esp_err_t cmd_display_mux_add_mode(CmdDisplayMux *mux, CmdDisplayMode mode);
|
||||
esp_err_t cmd_display_mux_remove_backend(CmdDisplayMux *mux, CmdDisplayBackend *backend);
|
||||
esp_err_t cmd_display_mux_init_backends(CmdDisplayMux *mux);
|
||||
esp_err_t cmd_display_mux_get_size(CmdDisplayMux *mux, cmd_size_t *size);
|
||||
void cmd_display_mux_deinit_backends(CmdDisplayMux *mux);
|
||||
esp_err_t cmd_display_mux_clear(CmdDisplayMux *mux);
|
||||
esp_err_t cmd_display_mux_present(CmdDisplayMux *mux, const CmdDisplayBuffer *buffer);
|
||||
esp_err_t cmd_display_mux_put_char(CmdDisplayMux *mux, int x, int y, char ch, cmd_display_attr_t attr);
|
||||
esp_err_t cmd_display_mux_write_text(CmdDisplayMux *mux, int x, int y, const char *text, cmd_display_attr_t attr);
|
||||
|
||||
esp_err_t cmd_display_console_init(CmdDisplayBackend *backend);
|
||||
void cmd_display_console_deinit(CmdDisplayBackend *backend);
|
||||
esp_err_t cmd_display_console_clear(CmdDisplayBackend *backend);
|
||||
esp_err_t cmd_display_console_present(CmdDisplayBackend *backend, const CmdDisplayBuffer *buffer);
|
||||
esp_err_t cmd_display_console_put_char(CmdDisplayBackend *backend, int x, int y, char ch, cmd_display_attr_t attr);
|
||||
esp_err_t cmd_display_console_write_text(CmdDisplayBackend *backend, int x, int y, const char *text, cmd_display_attr_t attr);
|
||||
esp_err_t cmd_display_console_get_size(CmdDisplayBackend *backend, cmd_size_t *size);
|
||||
|
||||
esp_err_t cmd_display_ft812_begin(CmdDisplayBackend *backend);
|
||||
void cmd_display_ft812_end(CmdDisplayBackend *backend);
|
||||
esp_err_t cmd_display_ft812_clear(CmdDisplayBackend *backend);
|
||||
esp_err_t cmd_display_ft812_present(CmdDisplayBackend *backend, const CmdDisplayBuffer *buffer);
|
||||
esp_err_t cmd_display_ft812_put_char(CmdDisplayBackend *backend, int x, int y, char ch, cmd_display_attr_t attr);
|
||||
esp_err_t cmd_display_ft812_write_text(CmdDisplayBackend *backend, int x, int y, const char *text, cmd_display_attr_t attr);
|
||||
esp_err_t cmd_display_ft812_get_size(CmdDisplayBackend *backend, cmd_size_t *size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
// --------------- cmd_fileops.h ---------------
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef CMD_FILEOPS_BUFFER_SIZE
|
||||
#define CMD_FILEOPS_BUFFER_SIZE 4096
|
||||
#endif
|
||||
|
||||
#ifndef CMD_FILEOPS_MAX_DEPTH
|
||||
#define CMD_FILEOPS_MAX_DEPTH 16
|
||||
#endif
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CMD_FILEOPS_OP_COPY = 0,
|
||||
CMD_FILEOPS_OP_MOVE,
|
||||
CMD_FILEOPS_OP_DELETE
|
||||
} cmd_fileops_op_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CMD_FILEOPS_FLAG_NONE = 0,
|
||||
CMD_FILEOPS_FLAG_RECURSIVE = 1u << 0,
|
||||
CMD_FILEOPS_FLAG_OVERWRITE = 1u << 1
|
||||
} cmd_fileops_flags_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
cmd_fileops_op_t op;
|
||||
const char *filename;
|
||||
uint64_t bytes_done;
|
||||
uint64_t bytes_total;
|
||||
size_t files_done;
|
||||
size_t files_total;
|
||||
bool can_cancel;
|
||||
} CmdFileopsProgress;
|
||||
|
||||
typedef bool (*CmdFileopsProgressFn)(const CmdFileopsProgress *progress, void *ctx);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t flags;
|
||||
CmdFileopsProgressFn progress;
|
||||
void *progress_ctx;
|
||||
} CmdFileopsOptions;
|
||||
|
||||
esp_err_t cmd_fileops_alloc_global_buffers();
|
||||
void cmd_fileops_free_global_buffers();
|
||||
|
||||
esp_err_t cmd_fileops_copy_path(CmdFsManager *fs,
|
||||
cmd_device_id_t src_device,
|
||||
const char *src_path,
|
||||
cmd_device_id_t dst_device,
|
||||
const char *dst_path,
|
||||
const CmdFileopsOptions *options);
|
||||
|
||||
esp_err_t cmd_fileops_copy_file(CmdFsManager *fs,
|
||||
cmd_device_id_t src_device,
|
||||
const char *src_path,
|
||||
cmd_device_id_t dst_device,
|
||||
const char *dst_path,
|
||||
const CmdFileopsOptions *options);
|
||||
|
||||
esp_err_t cmd_fileops_move_path(CmdFsManager *fs,
|
||||
cmd_device_id_t src_device,
|
||||
const char *src_path,
|
||||
cmd_device_id_t dst_device,
|
||||
const char *dst_path,
|
||||
const CmdFileopsOptions *options);
|
||||
|
||||
esp_err_t cmd_fileops_delete_path(CmdFsManager *fs,
|
||||
cmd_device_id_t device_id,
|
||||
const char *path,
|
||||
const CmdFileopsOptions *options);
|
||||
|
||||
esp_err_t cmd_fileops_copy_selected(CmdPanel *active,
|
||||
CmdPanel *passive,
|
||||
const CmdFileopsOptions *options);
|
||||
|
||||
esp_err_t cmd_fileops_move_selected(CmdPanel *active,
|
||||
CmdPanel *passive,
|
||||
const CmdFileopsOptions *options);
|
||||
|
||||
esp_err_t cmd_fileops_delete_selected(CmdPanel *panel,
|
||||
const CmdFileopsOptions *options);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
// --------------- cmd_fs.h ---------------
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define CMD_FS_DRIVER_MAX CMD_DEVICE_MAX
|
||||
|
||||
#ifndef CMD_FS_SD_ROOT_PATH
|
||||
#define CMD_FS_SD_ROOT_PATH "/sd"
|
||||
#endif
|
||||
|
||||
typedef struct CmdFsDriver CmdFsDriver;
|
||||
typedef struct CmdFsManager CmdFsManager;
|
||||
typedef struct CmdFsList CmdFsList;
|
||||
typedef struct CmdFsFile CmdFsFile;
|
||||
|
||||
typedef esp_err_t (*CmdFsMountFn)(CmdFsDriver *driver);
|
||||
typedef esp_err_t (*CmdFsUnmountFn)(CmdFsDriver *driver);
|
||||
typedef esp_err_t (*CmdFsListBeginFn)(CmdFsDriver *driver, const char *path, CmdFsList *list);
|
||||
typedef esp_err_t (*CmdFsListNextFn)(CmdFsDriver *driver, CmdFsList *list, cmd_file_entry_t *entry);
|
||||
typedef void (*CmdFsListCloseFn)(CmdFsDriver *driver, CmdFsList *list);
|
||||
typedef esp_err_t (*CmdFsStatFn)(CmdFsDriver *driver, const char *path, cmd_file_entry_t *entry);
|
||||
typedef esp_err_t (*CmdFsMkdirFn)(CmdFsDriver *driver, const char *path);
|
||||
typedef esp_err_t (*CmdFsRemoveFn)(CmdFsDriver *driver, const char *path);
|
||||
typedef esp_err_t (*CmdFsRenameFn)(CmdFsDriver *driver, const char *old_path, const char *new_path);
|
||||
typedef esp_err_t (*CmdFsOpenFn)(CmdFsDriver *driver, const char *path, CmdFsFile *file);
|
||||
typedef esp_err_t (*CmdFsReadFn)(CmdFsDriver *driver, CmdFsFile *file, void *dst, size_t size, size_t *out_size);
|
||||
typedef esp_err_t (*CmdFsWriteFn)(CmdFsDriver *driver, CmdFsFile *file, const void *src, size_t size, size_t *out_size);
|
||||
typedef esp_err_t (*CmdFsSeekFn)(CmdFsDriver *driver, CmdFsFile *file, int64_t offset, int whence);
|
||||
typedef esp_err_t (*CmdFsTellFn)(CmdFsDriver *driver, CmdFsFile *file, uint64_t *out_pos);
|
||||
typedef esp_err_t (*CmdFsCloseFn)(CmdFsDriver *driver, CmdFsFile *file);
|
||||
|
||||
struct CmdFsList
|
||||
{
|
||||
cmd_device_id_t device_id;
|
||||
char path[CMD_PATH_MAX];
|
||||
char abs_path[CMD_PATH_MAX];
|
||||
void *dir;
|
||||
size_t index;
|
||||
bool parent_pending;
|
||||
uint32_t flags;
|
||||
};
|
||||
|
||||
struct CmdFsFile
|
||||
{
|
||||
cmd_device_id_t device_id;
|
||||
FILE *fp;
|
||||
void *ctx;
|
||||
char name[CMD_FILE_NAME_MAX];
|
||||
uint32_t flags;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint64_t total_bytes;
|
||||
uint64_t free_bytes;
|
||||
}
|
||||
CmdFsSpaceInfo;
|
||||
|
||||
struct CmdFsDriver
|
||||
{
|
||||
cmd_device_id_t device_id;
|
||||
const char *name;
|
||||
const char *root_path;
|
||||
void *ctx;
|
||||
|
||||
CmdFsMountFn mount;
|
||||
CmdFsUnmountFn unmount;
|
||||
CmdFsListBeginFn list_begin;
|
||||
CmdFsListNextFn list_next;
|
||||
CmdFsListCloseFn list_close;
|
||||
CmdFsStatFn stat;
|
||||
CmdFsMkdirFn mkdir;
|
||||
CmdFsRemoveFn remove;
|
||||
CmdFsRenameFn rename;
|
||||
CmdFsOpenFn open_read;
|
||||
CmdFsOpenFn open_write;
|
||||
CmdFsReadFn read;
|
||||
CmdFsWriteFn write;
|
||||
CmdFsSeekFn seek;
|
||||
CmdFsTellFn tell;
|
||||
CmdFsCloseFn close;
|
||||
};
|
||||
|
||||
struct CmdFsManager
|
||||
{
|
||||
CmdFsDriver *drivers[CMD_FS_DRIVER_MAX];
|
||||
uint32_t registered_mask;
|
||||
uint32_t mounted_mask;
|
||||
};
|
||||
|
||||
extern CmdFsDriver cmd_fs_sd_driver;
|
||||
extern CmdFsDriver cmd_fs_fat_driver;
|
||||
extern CmdFsDriver cmd_fs_tsf_driver;
|
||||
|
||||
bool cmd_fs_is_valid_device_id(cmd_device_id_t device_id);
|
||||
|
||||
void cmd_fs_manager_init(CmdFsManager *manager);
|
||||
esp_err_t cmd_fs_manager_register_driver(CmdFsManager *manager, CmdFsDriver *driver);
|
||||
esp_err_t cmd_fs_manager_unregister_driver(CmdFsManager *manager, cmd_device_id_t device_id);
|
||||
CmdFsDriver *cmd_fs_manager_get_driver(CmdFsManager *manager, cmd_device_id_t device_id);
|
||||
const CmdFsDriver *cmd_fs_manager_get_driver_const(const CmdFsManager *manager, cmd_device_id_t device_id);
|
||||
esp_err_t cmd_fs_manager_register_default_drivers(CmdFsManager *manager);
|
||||
esp_err_t cmd_fs_manager_build_abs_path(const CmdFsManager *manager, cmd_device_id_t device_id, const char *current_path, const char *filename, char *out, size_t out_size);
|
||||
|
||||
esp_err_t cmd_fs_manager_mount(CmdFsManager *manager, cmd_device_id_t device_id);
|
||||
esp_err_t cmd_fs_manager_unmount(CmdFsManager *manager, cmd_device_id_t device_id);
|
||||
esp_err_t cmd_fs_manager_unmount_all(CmdFsManager *manager);
|
||||
esp_err_t cmd_fs_manager_list_begin(CmdFsManager *manager, cmd_device_id_t device_id, const char *path, CmdFsList *list);
|
||||
esp_err_t cmd_fs_manager_list_next(CmdFsManager *manager, CmdFsList *list, cmd_file_entry_t *entry);
|
||||
void cmd_fs_manager_list_close(CmdFsManager *manager, CmdFsList *list);
|
||||
esp_err_t cmd_fs_manager_stat(CmdFsManager *manager, cmd_device_id_t device_id, const char *path, cmd_file_entry_t *entry);
|
||||
esp_err_t cmd_fs_manager_space_info(CmdFsManager *manager, cmd_device_id_t device_id, CmdFsSpaceInfo *info);
|
||||
esp_err_t cmd_fs_manager_mkdir(CmdFsManager *manager, cmd_device_id_t device_id, const char *path);
|
||||
esp_err_t cmd_fs_manager_remove(CmdFsManager *manager, cmd_device_id_t device_id, const char *path);
|
||||
esp_err_t cmd_fs_manager_rename(CmdFsManager *manager, cmd_device_id_t device_id, const char *old_path, const char *new_path);
|
||||
esp_err_t cmd_fs_manager_open_read(CmdFsManager *manager, cmd_device_id_t device_id, const char *path, CmdFsFile *file);
|
||||
esp_err_t cmd_fs_manager_open_write(CmdFsManager *manager, cmd_device_id_t device_id, const char *path, CmdFsFile *file);
|
||||
esp_err_t cmd_fs_manager_read(CmdFsManager *manager, CmdFsFile *file, void *dst, size_t size, size_t *out_size);
|
||||
esp_err_t cmd_fs_manager_write(CmdFsManager *manager, CmdFsFile *file, const void *src, size_t size, size_t *out_size);
|
||||
esp_err_t cmd_fs_manager_seek(CmdFsManager *manager, CmdFsFile *file, int64_t offset, int whence);
|
||||
esp_err_t cmd_fs_manager_tell(CmdFsManager *manager, CmdFsFile *file, uint64_t *out_pos);
|
||||
esp_err_t cmd_fs_manager_close(CmdFsManager *manager, CmdFsFile *file);
|
||||
|
||||
esp_err_t cmd_fs_copy_path(const char *src, char *out, size_t out_size);
|
||||
esp_err_t cmd_fs_join_path(const char *left, const char *right, char *out, size_t out_size);
|
||||
esp_err_t cmd_fs_join3_path(const char *left, const char *middle, const char *right, char *out, size_t out_size);
|
||||
esp_err_t cmd_fs_parent_path(const char *path, char *out, size_t out_size);
|
||||
esp_err_t cmd_fs_extract_filename(const char *path, char *out, size_t out_size);
|
||||
const char *cmd_fs_filename_ptr(const char *path);
|
||||
bool cmd_fs_path_is_root(const char *path);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
// --------------- cmd_input.h ---------------
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define CMD_INPUT_WAIT_FOREVER UINT32_MAX
|
||||
#define CMD_INPUT_ESC_TIMEOUT_MS 120
|
||||
|
||||
typedef struct CmdInputBackend CmdInputBackend;
|
||||
|
||||
typedef esp_err_t (*cmd_input_begin_fn)(CmdInputBackend *backend);
|
||||
typedef void (*cmd_input_end_fn)(CmdInputBackend *backend);
|
||||
typedef esp_err_t (*cmd_input_poll_event_fn)(CmdInputBackend *backend, cmd_event_t *event, uint32_t timeout_ms);
|
||||
|
||||
struct CmdInputBackend
|
||||
{
|
||||
const char *name;
|
||||
void *ctx;
|
||||
cmd_input_begin_fn begin;
|
||||
cmd_input_end_fn end;
|
||||
cmd_input_poll_event_fn poll_event;
|
||||
};
|
||||
|
||||
void cmd_input_event_clear(cmd_event_t *event);
|
||||
void cmd_input_event_key(cmd_event_t *event, cmd_key_t key, uint32_t mods);
|
||||
void cmd_input_event_char(cmd_event_t *event, uint32_t ch, uint32_t mods);
|
||||
void cmd_input_event_quit(cmd_event_t *event);
|
||||
|
||||
esp_err_t cmd_input_begin(CmdInputBackend *backend);
|
||||
void cmd_input_end(CmdInputBackend *backend);
|
||||
esp_err_t cmd_input_poll_event(CmdInputBackend *backend, cmd_event_t *event, uint32_t timeout_ms);
|
||||
|
||||
CmdInputBackend *cmd_input_console_backend();
|
||||
CmdInputBackend *cmd_input_default_backend();
|
||||
|
||||
const char *cmd_input_key_name(cmd_key_t key);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
// --------------- cmd_panel.h ---------------
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef CMD_PANEL_MAX_ENTRIES
|
||||
#define CMD_PANEL_MAX_ENTRIES 1000
|
||||
#endif
|
||||
|
||||
#ifndef CMD_PANEL_DEFAULT_PAGE_STEP
|
||||
#define CMD_PANEL_DEFAULT_PAGE_STEP 10
|
||||
#endif
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CMD_PANEL_FLAG_NONE = 0,
|
||||
CMD_PANEL_FLAG_ACTIVE = 1u << 0,
|
||||
CMD_PANEL_FLAG_ERROR = 1u << 1,
|
||||
CMD_PANEL_FLAG_TRUNCATED = 1u << 2
|
||||
} cmd_panel_flags_t;
|
||||
|
||||
struct CmdPanel
|
||||
{
|
||||
CmdFsManager *fs;
|
||||
cmd_device_id_t device_id;
|
||||
char current_path[CMD_PATH_MAX];
|
||||
cmd_file_entry_t *entries;
|
||||
size_t capacity;
|
||||
size_t count;
|
||||
size_t cursor;
|
||||
size_t scroll;
|
||||
size_t view_rows;
|
||||
size_t selected_count;
|
||||
uint64_t selected_size;
|
||||
CmdFsSpaceInfo space_info;
|
||||
esp_err_t space_info_error;
|
||||
bool space_info_valid;
|
||||
bool active;
|
||||
uint32_t flags;
|
||||
esp_err_t last_error;
|
||||
};
|
||||
|
||||
esp_err_t cmd_panel_alloc_global_buffers();
|
||||
void cmd_panel_free_global_buffers();
|
||||
esp_err_t cmd_panel_alloc_entries(CmdPanel *panel, size_t capacity);
|
||||
void cmd_panel_free_entries(CmdPanel *panel);
|
||||
void cmd_panel_init(CmdPanel *panel, CmdFsManager *fs, cmd_device_id_t device_id);
|
||||
void cmd_panel_reset(CmdPanel *panel);
|
||||
void cmd_panel_set_active(CmdPanel *panel, bool active);
|
||||
bool cmd_panel_is_active(const CmdPanel *panel);
|
||||
esp_err_t cmd_panel_set_path(CmdPanel *panel, const char *path);
|
||||
esp_err_t cmd_panel_reload(CmdPanel *panel);
|
||||
esp_err_t cmd_panel_draw(CmdPanel *panel, CmdDisplayBuffer *buffer, const cmd_rect_t *rect);
|
||||
void cmd_panel_cursor_up(CmdPanel *panel);
|
||||
void cmd_panel_cursor_down(CmdPanel *panel);
|
||||
void cmd_panel_cursor_page_up(CmdPanel *panel);
|
||||
void cmd_panel_cursor_page_down(CmdPanel *panel);
|
||||
void cmd_panel_cursor_home(CmdPanel *panel);
|
||||
void cmd_panel_cursor_end(CmdPanel *panel);
|
||||
const cmd_file_entry_t *cmd_panel_get_selected_entry(const CmdPanel *panel);
|
||||
void cmd_panel_toggle_selection(CmdPanel *panel);
|
||||
void cmd_panel_invert_selection(CmdPanel *panel);
|
||||
void cmd_panel_clear_selection(CmdPanel *panel);
|
||||
void cmd_panel_update_selection_stats(CmdPanel *panel);
|
||||
bool cmd_panel_has_selection(const CmdPanel *panel);
|
||||
esp_err_t cmd_panel_change_device(CmdPanel *panel, cmd_device_id_t device_id);
|
||||
esp_err_t cmd_panel_enter_selected_dir(CmdPanel *panel);
|
||||
esp_err_t cmd_panel_go_parent(CmdPanel *panel);
|
||||
void cmd_panel_clamp_cursor(CmdPanel *panel);
|
||||
void cmd_panel_ensure_cursor_visible(CmdPanel *panel);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,179 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include "esp_system.h"
|
||||
#include "esp_console.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_vfs_dev.h" // Old headers for the aliasing functions
|
||||
|
||||
#include "driver/uart_vfs.h"
|
||||
|
||||
#include "driver/uart.h"
|
||||
#include "argtable3/argtable3.h"
|
||||
#include "linenoise/linenoise.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "ft8xx.h"
|
||||
#include "console.h"
|
||||
#include "tracker.h"
|
||||
#include "sfx.h"
|
||||
#include "tsf.h"
|
||||
#include "opl.h"
|
||||
#include "fatfs.h"
|
||||
#include "cmd.h"
|
||||
|
||||
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
|
||||
#include "wifi.h"
|
||||
#endif
|
||||
|
||||
#if SOC_SDMMC_HOST_SUPPORTED
|
||||
#include "sdmmc.h"
|
||||
#endif
|
||||
|
||||
#define PROMPT_STR "zifi32"
|
||||
#define CONSOLE_UART_RX_BUF_SIZE 1024
|
||||
#define CONSOLE_PROMPT_MAX_LEN 32
|
||||
|
||||
char prompt_buf[CONSOLE_PROMPT_MAX_LEN];
|
||||
const char *prompt = prompt_buf;
|
||||
|
||||
void console_register_commands()
|
||||
{
|
||||
esp_console_register_help_command();
|
||||
esp_console_register_system_commands();
|
||||
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
|
||||
esp_console_register_wifi_commands();
|
||||
#endif
|
||||
ft_console_register_system_commands();
|
||||
#if SOC_SDMMC_HOST_SUPPORTED
|
||||
sdmmc_console_register_system_commands();
|
||||
#endif
|
||||
xm_console_register_system_commands();
|
||||
sfx_console_register_system_commands();
|
||||
tsf_console_register_system_commands();
|
||||
opl_console_register_system_commands();
|
||||
fat_console_register_system_commands();
|
||||
cmd_console_register_system_commands();
|
||||
}
|
||||
|
||||
void console_update_prompt()
|
||||
{
|
||||
if (linenoiseIsDumbMode())
|
||||
{
|
||||
snprintf(prompt_buf, sizeof(prompt_buf), PROMPT_STR "> ");
|
||||
return;
|
||||
}
|
||||
|
||||
#if CONFIG_LOG_COLORS
|
||||
snprintf(prompt_buf, sizeof(prompt_buf), LOG_COLOR_I PROMPT_STR "> " LOG_RESET_COLOR);
|
||||
#else
|
||||
snprintf(prompt_buf, sizeof(prompt_buf), PROMPT_STR "> ");
|
||||
#endif
|
||||
}
|
||||
|
||||
void initialize_console()
|
||||
{
|
||||
// Drain stdout before reconfiguring it
|
||||
fflush(stdout);
|
||||
fsync(fileno(stdout));
|
||||
|
||||
// Disable buffering on stdin
|
||||
setvbuf(stdin, NULL, _IONBF, 0);
|
||||
log_sram_used(__FILE_NAME__ ": setvbuf");
|
||||
|
||||
/* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
|
||||
uart_vfs_dev_port_set_rx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CR);
|
||||
|
||||
/* Move the caret to the beginning of the next line on '\n' */
|
||||
uart_vfs_dev_port_set_tx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CRLF);
|
||||
|
||||
// Install UART driver for interrupt-driven reads and writes
|
||||
ESP_ERROR_CHECK(uart_driver_install((uart_port_t)CONFIG_ESP_CONSOLE_UART_NUM, CONSOLE_UART_RX_BUF_SIZE, 0, 0, NULL, 0));
|
||||
log_sram_used(__FILE_NAME__ ": uart_driver_install");
|
||||
|
||||
// Tell VFS to use UART driver
|
||||
uart_vfs_dev_use_driver(CONFIG_ESP_CONSOLE_UART_NUM);
|
||||
|
||||
// Initialize the console
|
||||
esp_console_config_t console_config =
|
||||
{
|
||||
.max_cmdline_length = 256,
|
||||
.max_cmdline_args = 128,
|
||||
#if CONFIG_LOG_COLORS
|
||||
.hint_color = atoi(LOG_COLOR_CYAN)
|
||||
#endif
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_init(&console_config));
|
||||
|
||||
// Configure linenoise line completion library.
|
||||
// Single-line mode sends less data on each key press and is much more stable on UART.
|
||||
linenoiseSetMultiLine(0);
|
||||
|
||||
// Detect whether the terminal supports escape sequences.
|
||||
if (linenoiseProbe() != 0)
|
||||
linenoiseSetDumbMode(1);
|
||||
|
||||
// Tell linenoise where to get command completions.
|
||||
linenoiseSetCompletionCallback(&esp_console_get_completion);
|
||||
|
||||
// Hints trigger additional redraw traffic on every key press.
|
||||
// Disabling them makes repeated backspace handling much more stable.
|
||||
// linenoiseSetHintsCallback((linenoiseHintsCallback*)&esp_console_get_hint);
|
||||
|
||||
// Set command history size
|
||||
linenoiseHistorySetMaxLen(100);
|
||||
|
||||
// Set command maximum length
|
||||
linenoiseSetMaxLineLen(console_config.max_cmdline_length);
|
||||
|
||||
// Don't return empty lines
|
||||
linenoiseAllowEmpty(false);
|
||||
|
||||
console_update_prompt();
|
||||
|
||||
// Register commands
|
||||
console_register_commands();
|
||||
|
||||
log_sram_used(__FILE_NAME__ ": initialize_console end");
|
||||
}
|
||||
|
||||
void console_task(void *arg)
|
||||
{
|
||||
_delay_ms(30);
|
||||
|
||||
printf("\n"
|
||||
"Type 'help' to get the list of commands.\n"
|
||||
"Use UP/DOWN arrows to navigate through command history.\n"
|
||||
"Press TAB when typing command name to auto-complete.\n");
|
||||
|
||||
while (true)
|
||||
{
|
||||
char* line = linenoise(prompt);
|
||||
|
||||
if (line == NULL)
|
||||
continue;
|
||||
|
||||
if (strlen(line) > 0)
|
||||
linenoiseHistoryAdd(line);
|
||||
|
||||
int ret;
|
||||
esp_err_t err = esp_console_run(line, &ret);
|
||||
|
||||
if (err == ESP_ERR_NOT_FOUND)
|
||||
printf("Unrecognized command\n");
|
||||
else if (err == ESP_ERR_INVALID_ARG)
|
||||
{
|
||||
}
|
||||
else if (err == ESP_OK && ret != ESP_OK)
|
||||
printf("Command returned non-zero error code: 0x%x (%s)\n", ret, esp_err_to_name(ret));
|
||||
else if (err != ESP_OK)
|
||||
printf("Internal error: %s\n", esp_err_to_name(err));
|
||||
|
||||
linenoiseFree(line);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
void initialize_console();
|
||||
void console_task(void *arg);
|
||||
void esp_console_register_system_commands();
|
||||
@@ -0,0 +1,359 @@
|
||||
|
||||
#include "main.h"
|
||||
|
||||
//------------------- DeHrust --------------------------
|
||||
|
||||
/* BBStream class */
|
||||
class BBStream
|
||||
{
|
||||
private:
|
||||
u8* base;
|
||||
u8* p;
|
||||
int idx;
|
||||
int len;
|
||||
bool eof;
|
||||
u16 bits;
|
||||
|
||||
public:
|
||||
BBStream(u8* from, int blockSize)
|
||||
{
|
||||
base = p = from;
|
||||
|
||||
len = blockSize;
|
||||
idx = 0;
|
||||
eof = false;
|
||||
|
||||
bits = getByte();
|
||||
bits += 256 * getByte();
|
||||
}
|
||||
|
||||
u8 getByte(void)
|
||||
{
|
||||
if (p - base == len)
|
||||
{
|
||||
eof = true; return 0;
|
||||
}
|
||||
return *p++;
|
||||
}
|
||||
|
||||
u8 getBit()
|
||||
{
|
||||
u16 mask[] = { 0x8000, 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100, 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1 };
|
||||
|
||||
u8 bit = (bits & mask[idx]) ? 1 : 0;
|
||||
|
||||
if (idx == 15)
|
||||
{
|
||||
bits = getByte();
|
||||
bits += 256 * getByte();
|
||||
}
|
||||
|
||||
idx = (idx + 1) % 16;
|
||||
return bit;
|
||||
}
|
||||
|
||||
u8 getBits(int n)
|
||||
{
|
||||
u8 r = 0;
|
||||
|
||||
do
|
||||
{
|
||||
r = 2 * r + getBit();
|
||||
} while(--n);
|
||||
return r;
|
||||
}
|
||||
|
||||
bool error(void)
|
||||
{
|
||||
return eof;
|
||||
}
|
||||
};
|
||||
|
||||
/* depacker */
|
||||
u16 dehrust(u8 *dst, u8 *src, int size)
|
||||
{
|
||||
BBStream s(src, size);
|
||||
|
||||
if (!dst || !src)
|
||||
return 0;
|
||||
|
||||
u8 *to = dst;
|
||||
|
||||
*to++ = s.getByte();
|
||||
|
||||
u8 noBits = 2;
|
||||
u8 mask[] = { 0, 0, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0 };
|
||||
|
||||
while (!s.error())
|
||||
{
|
||||
while (s.getBit())
|
||||
*to++ = s.getByte();
|
||||
|
||||
u16 len = 0;
|
||||
u8 bb /* = 0*/;
|
||||
|
||||
do
|
||||
{
|
||||
bb = s.getBits(2);
|
||||
len += bb;
|
||||
} while(bb == 0x03 && len != 0x0f);
|
||||
|
||||
short offset = 0;
|
||||
|
||||
if (len == 0)
|
||||
{
|
||||
offset = 0xfff8 + s.getBits(3);
|
||||
{ u8 t = to[offset]; *to++ = t; }
|
||||
continue;
|
||||
}
|
||||
|
||||
if (len == 1)
|
||||
{
|
||||
u8 code = s.getBits(2);
|
||||
|
||||
if (code == 2)
|
||||
{
|
||||
u8 b = s.getByte();
|
||||
if (b >= 0xe0)
|
||||
{
|
||||
b <<= 1; ++b; // rlca
|
||||
b ^= 2; // xor c
|
||||
|
||||
if (b == 0xff)
|
||||
{
|
||||
++noBits; continue;
|
||||
}
|
||||
|
||||
offset = 0xff00 + b - 0x0f;
|
||||
|
||||
{ u8 t = to[offset]; *to++ = t; }
|
||||
*to++ = s.getByte();
|
||||
{ u8 t = to[offset]; *to++ = t; }
|
||||
continue;
|
||||
}
|
||||
offset = 0xff00 + b;
|
||||
}
|
||||
|
||||
if (code == 0 || code == 1)
|
||||
{
|
||||
offset = s.getByte();
|
||||
offset += 256 * (code ? 0xfe : 0xfd);
|
||||
}
|
||||
if (code == 3)
|
||||
offset = 0xffe0 + s.getBits(5);
|
||||
|
||||
for (u8 i = 0; i < 2; ++i)
|
||||
{ u8 t = to[offset]; *to++ = t; }
|
||||
continue;
|
||||
}
|
||||
|
||||
if (len == 3)
|
||||
{
|
||||
if (s.getBit())
|
||||
{
|
||||
offset = 0xfff0 + s.getBits(4);
|
||||
{ u8 t = to[offset]; *to++ = t; }
|
||||
*to++ = s.getByte();
|
||||
{ u8 t = to[offset]; *to++ = t; }
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s.getBit())
|
||||
{
|
||||
u8 noBytes = 6 + s.getBits(4);
|
||||
for (u8 i = 0; i < 2 * noBytes; ++i)
|
||||
*to++ = s.getByte();
|
||||
continue;
|
||||
}
|
||||
|
||||
len = s.getBits(7);
|
||||
if (len == 0x0f)
|
||||
break; // EOF
|
||||
if (len < 0x0f)
|
||||
len = 256 * len + s.getByte();
|
||||
}
|
||||
|
||||
if (len == 2)
|
||||
++len;
|
||||
|
||||
u8 code = s.getBits(2);
|
||||
|
||||
if (code == 1)
|
||||
{
|
||||
u8 b = s.getByte();
|
||||
|
||||
if (b >= 0xe0)
|
||||
{
|
||||
if (len > 3)
|
||||
return false;
|
||||
|
||||
b <<= 1; ++b; // rlca
|
||||
b ^= 3; // xor c
|
||||
|
||||
offset = 0xff00 + b - 0x0f;
|
||||
|
||||
{ u8 t = to[offset]; *to++ = t; }
|
||||
*to++ = s.getByte();
|
||||
{ u8 t = to[offset]; *to++ = t; }
|
||||
continue;
|
||||
}
|
||||
offset = 0xff00 + b;
|
||||
}
|
||||
|
||||
if (code == 0)
|
||||
offset = 0xfe00 + s.getByte();
|
||||
if (code == 2)
|
||||
offset = 0xffe0 + s.getBits(5);
|
||||
if (code == 3)
|
||||
{
|
||||
offset = 256 * (mask[noBits] + s.getBits(noBits));
|
||||
offset += s.getByte();
|
||||
}
|
||||
|
||||
for (u16 i = 0; i < len; ++i)
|
||||
{ u8 t = to[offset]; *to++ = t; }
|
||||
}
|
||||
|
||||
return to - dst;
|
||||
}
|
||||
|
||||
//------------------- DeMegaLZ --------------------------
|
||||
|
||||
class deMLZ
|
||||
{
|
||||
private:
|
||||
u8 *from;
|
||||
u8 *to;
|
||||
u8 bitstream;
|
||||
int bitcount;
|
||||
|
||||
public:
|
||||
deMLZ(u8 *dst, u8 *src)
|
||||
{
|
||||
from = src;
|
||||
to = dst;
|
||||
}
|
||||
|
||||
void init_bitstream(void)
|
||||
{
|
||||
bitstream = get_byte();
|
||||
bitcount = 8;
|
||||
}
|
||||
|
||||
u8 get_byte(void)
|
||||
{
|
||||
return *from++;
|
||||
}
|
||||
|
||||
void put_byte(u8 val)
|
||||
{
|
||||
*to++ = val;
|
||||
}
|
||||
|
||||
void repeat(u32 disp, int num)
|
||||
{
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
u8 val = *(to - disp);
|
||||
put_byte(val);
|
||||
}
|
||||
}
|
||||
|
||||
// gets specified number of bits from bitstream
|
||||
// returns them LSB-aligned
|
||||
u32 get_bits(int count)
|
||||
{
|
||||
u32 bits = 0;
|
||||
|
||||
while (count--)
|
||||
{
|
||||
if (bitcount--)
|
||||
{
|
||||
bits <<= 1;
|
||||
bits |= 1 & (bitstream >> 7);
|
||||
bitstream <<= 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
init_bitstream();
|
||||
count++; // repeat loop once more
|
||||
}
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
int get_bigdisp(void)
|
||||
{
|
||||
u32 bits;
|
||||
|
||||
// inter displacement
|
||||
if (get_bits(1))
|
||||
{
|
||||
bits = get_bits(4);
|
||||
return 0x1100 - (bits << 8) - get_byte();
|
||||
}
|
||||
|
||||
// shorter displacement
|
||||
else
|
||||
return 256 - get_byte();
|
||||
}
|
||||
};
|
||||
|
||||
// depacker
|
||||
void demlz(u8 *dst, u8 *src, int size)
|
||||
{
|
||||
deMLZ s(dst, src);
|
||||
u32 done = 0;
|
||||
int i;
|
||||
|
||||
// get first byte of packed file and write to output
|
||||
s.put_byte(s.get_byte());
|
||||
|
||||
// second byte goes to bitstream
|
||||
s.init_bitstream();
|
||||
|
||||
// actual depacking loop!
|
||||
do
|
||||
{
|
||||
// get 1st bit - either OUTBYTE or beginning of LZ code
|
||||
// OUTBYTE
|
||||
if (s.get_bits(1))
|
||||
s.put_byte(s.get_byte());
|
||||
|
||||
// LZ code
|
||||
else
|
||||
{
|
||||
switch (s.get_bits(2))
|
||||
{
|
||||
case 0: // 000
|
||||
s.repeat(8 - s.get_bits(3), 1);
|
||||
break;
|
||||
|
||||
case 1: // 001
|
||||
s.repeat(256 - s.get_byte(), 2);
|
||||
break;
|
||||
|
||||
case 2: // 010
|
||||
s.repeat(s.get_bigdisp(), 3);
|
||||
break;
|
||||
|
||||
case 3: // 011
|
||||
// extract num of length bits
|
||||
for (i = 1; !s.get_bits(1); i++)
|
||||
;
|
||||
|
||||
// check for exit code
|
||||
if (i == 9)
|
||||
done = 1;
|
||||
else if (i <= 7)
|
||||
{
|
||||
// get length bits itself
|
||||
int bits = s.get_bits(i);
|
||||
s.repeat(s.get_bigdisp(), 2 + (1 << i) + bits);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (!done);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
#pragma once
|
||||
#include "main.h"
|
||||
|
||||
u16 dehrust(u8 *dst, u8 *src, int size);
|
||||
void demlz(u8 *dst, u8 *src, int size);
|
||||
@@ -0,0 +1,244 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <elf.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "esp_spi_defs.h"
|
||||
#include "mem_obj.h"
|
||||
#include "elf.cpp.h"
|
||||
|
||||
int load_elf(u8 *elf, MEM_OBJ *obj, int opt)
|
||||
{
|
||||
enum
|
||||
{
|
||||
TYPE_NONE,
|
||||
TYPE_TEXT,
|
||||
TYPE_RODATA,
|
||||
TYPE_DATA,
|
||||
TYPE_BSS,
|
||||
};
|
||||
|
||||
Elf32_Ehdr* elf_header = (Elf32_Ehdr*)elf;
|
||||
Elf32_Shdr* section_headers = (Elf32_Shdr*)(elf + elf_header->e_shoff);
|
||||
#ifdef VERBOSE
|
||||
Elf32_Shdr* shstrtab_header = §ion_headers[elf_header->e_shstrndx];
|
||||
const char* shstrtab = (const char*)(elf + shstrtab_header->sh_offset);
|
||||
#endif
|
||||
u32 entry = 0;
|
||||
int size = 0;
|
||||
|
||||
int num_sec = elf_header->e_shnum;
|
||||
|
||||
Elf32_Rela* rld = NULL;
|
||||
int rld_num = 0;
|
||||
|
||||
// Parse sections to determine type and find dynamic relocation section
|
||||
for (int i = 0; i < num_sec; i++)
|
||||
{
|
||||
Elf32_Shdr* section = §ion_headers[i];
|
||||
|
||||
if (!section->sh_addr) continue;
|
||||
|
||||
section->sh_info = TYPE_NONE;
|
||||
section->sh_link = 0;
|
||||
|
||||
switch (section->sh_type)
|
||||
{
|
||||
case SHT_RELA:
|
||||
section->sh_type = TYPE_NONE;
|
||||
rld = (Elf32_Rela*)&elf[section->sh_offset];
|
||||
rld_num = section->sh_size / sizeof(Elf32_Rela);
|
||||
break;
|
||||
|
||||
case SHT_NOBITS:
|
||||
section->sh_type = TYPE_BSS;
|
||||
break;
|
||||
|
||||
case SHT_PROGBITS:
|
||||
if (section->sh_flags & SHF_EXECINSTR)
|
||||
{
|
||||
section->sh_type = TYPE_TEXT;
|
||||
section->sh_link = 1; // .text is always used if no symbol refere it
|
||||
}
|
||||
|
||||
else if (section->sh_flags & SHF_WRITE)
|
||||
section->sh_type = TYPE_DATA;
|
||||
|
||||
else
|
||||
section->sh_type = TYPE_RODATA;
|
||||
break;
|
||||
|
||||
default:
|
||||
section->sh_type = TYPE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse relocation symbols to find used sections
|
||||
for (int i = 0; i < rld_num; i++)
|
||||
{
|
||||
u32 rld_offs = rld[i].r_offset;
|
||||
u32 *sym_offs = (u32*)&elf[rld_offs];
|
||||
u32 sym_val = *sym_offs;
|
||||
|
||||
for (int j = 0; j < num_sec; j++)
|
||||
{
|
||||
Elf32_Shdr* section = §ion_headers[j];
|
||||
|
||||
if (section->sh_addr)
|
||||
{
|
||||
if ((sym_val >= section->sh_addr) && (sym_val <= (section->sh_addr + section->sh_size - 1)))
|
||||
{
|
||||
section->sh_link++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate memory for sections
|
||||
#ifdef VERBOSE
|
||||
printf("\nSections used:\n");
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < num_sec; i++)
|
||||
{
|
||||
Elf32_Shdr* section = §ion_headers[i];
|
||||
|
||||
if (!section->sh_addr) continue;
|
||||
if (!section->sh_link) continue;
|
||||
|
||||
section->sh_info = 0;
|
||||
|
||||
switch (section->sh_type)
|
||||
{
|
||||
case TYPE_TEXT:
|
||||
section->sh_info = (u32)heap_caps_aligned_alloc(section->sh_addralign, section->sh_size, MALLOC_CAP_INTERNAL);
|
||||
if (!section->sh_info) goto cleanup;
|
||||
entry = elf_header->e_entry - section->sh_offset + section->sh_info + 0x6F0000;
|
||||
obj->text = (void*)section->sh_info;
|
||||
obj->sz_text = section->sh_size;
|
||||
obj->entry = (void*)entry;
|
||||
size += section->sh_size;
|
||||
break;
|
||||
|
||||
case TYPE_BSS:
|
||||
#ifdef CONFIG_SPIRAM
|
||||
section->sh_info = (u32)heap_caps_aligned_alloc(section->sh_addralign, section->sh_size, (opt & ESP_OPT_BSS_SRAM) ? MALLOC_CAP_INTERNAL : MALLOC_CAP_SPIRAM);
|
||||
#else
|
||||
section->sh_info = (u32)heap_caps_aligned_alloc(section->sh_addralign, section->sh_size, MALLOC_CAP_INTERNAL);
|
||||
#endif
|
||||
if (!section->sh_info) goto cleanup;
|
||||
obj->bss = (void*)section->sh_info;
|
||||
obj->sz_bss = section->sh_size;
|
||||
size += section->sh_size;
|
||||
break;
|
||||
|
||||
case TYPE_DATA:
|
||||
#ifdef CONFIG_SPIRAM
|
||||
section->sh_info = (u32)heap_caps_aligned_alloc(section->sh_addralign, section->sh_size, (opt & ESP_OPT_DATA_SRAM) ? MALLOC_CAP_INTERNAL : MALLOC_CAP_SPIRAM);
|
||||
#else
|
||||
section->sh_info = (u32)heap_caps_aligned_alloc(section->sh_addralign, section->sh_size, MALLOC_CAP_INTERNAL);
|
||||
#endif
|
||||
if (!section->sh_info) goto cleanup;
|
||||
obj->data = (void*)section->sh_info;
|
||||
obj->sz_data = section->sh_size;
|
||||
size += section->sh_size;
|
||||
break;
|
||||
|
||||
case TYPE_RODATA:
|
||||
#ifdef CONFIG_SPIRAM
|
||||
section->sh_info = (u32)heap_caps_aligned_alloc(section->sh_addralign, section->sh_size, (opt & ESP_OPT_RODATA_SRAM) ? MALLOC_CAP_INTERNAL : MALLOC_CAP_SPIRAM);
|
||||
#else
|
||||
section->sh_info = (u32)heap_caps_aligned_alloc(section->sh_addralign, section->sh_size, MALLOC_CAP_INTERNAL);
|
||||
#endif
|
||||
if (!section->sh_info) goto cleanup;
|
||||
obj->rodata = (void*)section->sh_info;
|
||||
obj->sz_rodata = section->sh_size;
|
||||
size += section->sh_size;
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef VERBOSE
|
||||
const char* section_name = shstrtab + section->sh_name;
|
||||
printf("%-10s Addr: %06lX Size: %-5ld Symbols: %-3ld Alloc: %lX Align: %ld\n", section_name, section->sh_addr, section->sh_size, section->sh_link, section->sh_info, section->sh_addralign);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Relocate dynamic symbols
|
||||
#ifdef VERBOSE
|
||||
printf("\nRelocated symbols:\n");
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < rld_num; i++)
|
||||
{
|
||||
u32 rld_offs = rld[i].r_offset;
|
||||
u32 *sym_offs = (u32*)&elf[rld_offs];
|
||||
u32 sym_val = *sym_offs;
|
||||
u32 sym_addr = 0;
|
||||
#ifdef VERBOSE
|
||||
const char* name = "";
|
||||
#endif
|
||||
|
||||
for (int j = 0; j < num_sec; j++)
|
||||
{
|
||||
Elf32_Shdr* section = §ion_headers[j];
|
||||
|
||||
if (section->sh_addr)
|
||||
{
|
||||
if ((sym_val >= section->sh_addr) && (sym_val <= (section->sh_addr + section->sh_size - 1)))
|
||||
{
|
||||
sym_addr = sym_val - section->sh_offset + section->sh_info + ((section->sh_type == TYPE_TEXT) ? 0x6F0000 : 0);
|
||||
*sym_offs = sym_addr;
|
||||
#ifdef VERBOSE
|
||||
name = shstrtab + section->sh_name;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef VERBOSE
|
||||
printf("%-4d Offs: %-5lX Value: %-6lX Addr: %lX Section: %s\n", i, rld_offs, sym_val, sym_addr, name);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Initialize memory for sections
|
||||
for (int i = 0; i < num_sec; i++)
|
||||
{
|
||||
Elf32_Shdr* section = §ion_headers[i];
|
||||
|
||||
if (!section->sh_addr) continue;
|
||||
if (!section->sh_link) continue;
|
||||
|
||||
switch (section->sh_type)
|
||||
{
|
||||
case TYPE_TEXT:
|
||||
case TYPE_DATA:
|
||||
case TYPE_RODATA:
|
||||
if (section->sh_info) memcpy((void*)section->sh_info, &elf[section->sh_offset], section->sh_size);
|
||||
break;
|
||||
|
||||
case TYPE_BSS:
|
||||
if (section->sh_info) memset((void*)section->sh_info, 0, section->sh_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef VERBOSE
|
||||
printf("\nEntry: %lX\n", entry);
|
||||
#endif
|
||||
|
||||
return size;
|
||||
|
||||
cleanup:
|
||||
if (obj->text) free(obj->text);
|
||||
if (obj->rodata) free(obj->rodata);
|
||||
if (obj->data) free(obj->data);
|
||||
if (obj->bss) free(obj->bss);
|
||||
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
int load_elf(u8 *elf, MEM_OBJ *obj, int opt);
|
||||
@@ -0,0 +1,375 @@
|
||||
#pragma once
|
||||
|
||||
#define ZIFI_STR_HELPER(x) #x
|
||||
#define ZIFI_STR(x) ZIFI_STR_HELPER(x)
|
||||
|
||||
#define PROD_VER0 0
|
||||
#define PROD_VER1 6
|
||||
#define PROD_VER2 4
|
||||
#define PROD_VER_STRING ZIFI_STR(PROD_VER0) "." ZIFI_STR(PROD_VER1) "." ZIFI_STR(PROD_VER2)
|
||||
#define CP_STRING "ESP32 SPI WiFi Module, ver." PROD_VER_STRING ", (c) TS-Labs"
|
||||
|
||||
#define API_VER 1
|
||||
#define FEAT_VER 10
|
||||
#define API_VER_STRING ZIFI_STR(API_VER)
|
||||
#define FEAT_VER_STRING ZIFI_STR(FEAT_VER)
|
||||
#define API_STRING "API version: " API_VER_STRING ", feature version: " FEAT_VER_STRING
|
||||
|
||||
// Registers
|
||||
enum
|
||||
{
|
||||
ESP_REG_COMMAND = 0x00, // 1 byte
|
||||
ESP_REG_STATUS = 0x01, // 1 byte
|
||||
|
||||
// String commands
|
||||
ESP_REG_STRING_TYPE = 0x02, // 1 byte
|
||||
ESP_REG_STRING_SIZE = 0x02, // 1 byte
|
||||
ESP_REG_STRING_DATA = 0x03, // byte array
|
||||
|
||||
// Version commands
|
||||
ESP_REG_API = 0x02, // 1 byte
|
||||
ESP_REG_FEAT = 0x03, // 2 bytes
|
||||
ESP_REG_VER0 = 0x05, // 1 byte
|
||||
ESP_REG_VER1 = 0x06, // 1 byte
|
||||
ESP_REG_VER2 = 0x07, // 1 byte
|
||||
|
||||
// Object commands and data transmission commands
|
||||
ESP_REG_OBJ_TYPE = 0x02, // 1 byte
|
||||
ESP_REG_DATA_SIZE = 0x03, // 4 bytes
|
||||
ESP_REG_DATA_OFFSET = 0x07, // 4 bytes
|
||||
ESP_REG_OBJ_HANDLE = 0x0B, // 1 byte
|
||||
|
||||
// Lib commands
|
||||
ESP_REG_FUNC = 0x02, // 1 byte
|
||||
ESP_REG_OPT = 0x03, // 1 byte
|
||||
ESP_REG_ARG = 0x03, // 4 bytes
|
||||
ESP_REG_RETVAL = 0x07, // 4 bytes
|
||||
ESP_REG_ARR1_HANDLE = 0x0C, // 1 byte
|
||||
ESP_REG_ARR2_HANDLE = 0x0D, // 1 byte
|
||||
ESP_REG_ARR3_HANDLE = 0x0E, // 1 byte
|
||||
ESP_REG_LIB_HANDLE = 0x0F, // 1 byte
|
||||
|
||||
// Network commands
|
||||
ESP_REG_NETSTATE = 0x02,
|
||||
ESP_REG_IP = 0x2C, // 4 bytes
|
||||
ESP_REG_OWN_IP = 0x30, // 4 bytes
|
||||
ESP_REG_MASK = 0x34, // 4 bytes
|
||||
ESP_REG_GATE = 0x38, // 4 bytes
|
||||
|
||||
// Stats commands
|
||||
ESP_EXEC_TIME = 0x3C, // 4 bytes
|
||||
|
||||
// SFX commands
|
||||
ESP_REG_SFX_CHANNEL = 0x02, // 1 byte
|
||||
ESP_REG_SFX_GROUP = 0x03, // 1 byte
|
||||
ESP_REG_SFX_VOLUME = 0x04, // 1 byte
|
||||
ESP_REG_SFX_PAN = 0x05, // 1 byte
|
||||
ESP_REG_SFX_PITCH = 0x06, // 2 bytes, Q4.8 little-endian
|
||||
|
||||
ESP_REG_SFX_STATE_ACTIVE = 0x10, // 1 byte
|
||||
ESP_REG_SFX_STATE_HANDLE = 0x11, // 1 byte
|
||||
ESP_REG_SFX_STATE_GROUP = 0x12, // 1 byte
|
||||
ESP_REG_SFX_STATE_ORDER = 0x13, // 1 byte
|
||||
ESP_REG_SFX_STATE_VOLUME = 0x14, // 1 byte
|
||||
ESP_REG_SFX_STATE_PAN = 0x15, // 1 byte
|
||||
ESP_REG_SFX_STATE_PITCH = 0x16, // 2 bytes, Q4.8 little-endian
|
||||
ESP_REG_SFX_STATE_RATE = 0x18, // 4 bytes
|
||||
ESP_REG_SFX_STATE_FRAMES = 0x1C, // 4 bytes
|
||||
ESP_REG_SFX_STATE_POSITION = 0x20, // 4 bytes
|
||||
ESP_REG_SFX_STATE_BITS = 0x24, // 2 bytes
|
||||
ESP_REG_SFX_STATE_CHANNELS = 0x26, // 1 byte
|
||||
ESP_REG_SFX_STATE_SIGNED = 0x27, // 1 byte
|
||||
|
||||
// Track (XM/MOD/S3M) get-state/get-info commands
|
||||
ESP_REG_TRACK_STATUS = 0x28, // 1 byte: TRACK_OBJ_ST_STOPPED / TRACK_OBJ_ST_PLAYING
|
||||
ESP_REG_TRACK_IS_CURRENT = 0x29, // 1 byte: 1 if this handle is the one currently loaded in the player
|
||||
ESP_REG_TRACK_PATTERN_INDEX = 0x2A, // 1 byte: position in the order/pattern-table list
|
||||
ESP_REG_TRACK_PATTERN = 0x2B, // 1 byte: pattern number
|
||||
ESP_REG_TRACK_ROW = 0x2C, // 1 byte: current row within the pattern
|
||||
ESP_REG_TRACK_SAMPLES = 0x2D, // 4 bytes: samples elapsed since track start
|
||||
ESP_REG_TRACK_BPM = 0x31, // 2 bytes
|
||||
ESP_REG_TRACK_TEMPO = 0x33, // 2 bytes
|
||||
ESP_REG_TRACK_LOOP_COUNT = 0x35, // 1 byte
|
||||
ESP_REG_TRACK_TOTAL_SAMPLES = 0x36, // 4 bytes: length of one full pass through the song, computed once at load time
|
||||
ESP_REG_TRACK_MAX_LOOP_COUNT = 0x3A, // 1 byte: see ESP_CMD_TRACK_SET_PARAMS
|
||||
|
||||
// VFS bridge commands (SMB2 network share <-> Wild Commander local storage)
|
||||
ESP_REG_VFS_PENDING = 0x02, // 1 byte: 1 if a request is waiting, else 0
|
||||
// NOTE: ESP_CMD_VFS_POLL's output also includes ESP_REG_DATA_SIZE (0x03,
|
||||
// 4 bytes) - VFS_OP must not overlap that range, unlike most register
|
||||
// reuse in this header, since both are read back from the SAME command.
|
||||
ESP_REG_VFS_OP = 0x36, // 1 byte: VFS_OP_* (see below) - meaning of the payload from ESP_CMD_VFS_GET_REQUEST_DATA
|
||||
ESP_REG_VFS_STATUS = 0x02, // 1 byte: i: 0=success, nonzero=error (ESP_CMD_VFS_SUBMIT_RESULT)
|
||||
};
|
||||
|
||||
// Commands
|
||||
enum
|
||||
{
|
||||
ESP_CMD_NOP = 0x00, // (no parameters)
|
||||
ESP_CMD_GET_INFO_STR = 0x01, // i: ESP_REG_STRING_TYPE
|
||||
// o: ESP_REG_STRING_SIZE, ESP_REG_STRING_DATA
|
||||
ESP_CMD_GET_VER = 0x02, // o: ESP_REG_API, ESP_REG_FEAT, ESP_REG_VER0..2
|
||||
ESP_CMD_GET_CHIP_INFO = 0x03, // +++
|
||||
|
||||
ESP_CMD_GET_NETSTATE = 0x11, // o: ESP_REG_NETSTATE
|
||||
ESP_CMD_WSCAN = 0x12, // (no parameters)
|
||||
ESP_CMD_SET_AP_NAME = 0x13, // i: ESP_REG_STRING_SIZE, ESP_REG_STRING_DATA
|
||||
ESP_CMD_SET_AP_PWD = 0x14, // i: ESP_REG_STRING_SIZE, ESP_REG_STRING_DATA
|
||||
ESP_CMD_AP_CONNECT = 0x16, // (no parameters)
|
||||
ESP_CMD_AP_DISCONNECT = 0x17, // (no parameters)
|
||||
ESP_CMD_SET_URL = 0x18, // (no parameters)
|
||||
ESP_CMD_GET_IP = 0x19, // o: ESP_REG_IP
|
||||
ESP_CMD_HTTP_GET = 0x20, // +++
|
||||
ESP_CMD_HTTPS_GET = 0x21, // (no parameters)
|
||||
// o: ESP_REG_OBJ_HANDLE, ESP_REG_DATA_SIZE
|
||||
ESP_CMD_GOPHER_GET = 0x22, // (no parameters)
|
||||
// o: ESP_REG_OBJ_HANDLE, ESP_REG_DATA_SIZE
|
||||
ESP_CMD_HTTP_STREAM_START = 0x23, // (no parameters)
|
||||
// o: ESP_REG_DATA_SIZE (total size)
|
||||
ESP_CMD_HTTPS_STREAM_START = 0x24, // (no parameters)
|
||||
// o: ESP_REG_DATA_SIZE (total size)
|
||||
ESP_CMD_GOPHER_STREAM_START = 0x25, // (no parameters)
|
||||
// o: ESP_REG_DATA_SIZE (total size)
|
||||
ESP_CMD_STREAM_READ = 0x26, // (no parameters)
|
||||
// o: ESP_REG_DATA_SIZE (chunk size)
|
||||
// (DMA read like ESP_CMD_READ_OBJECT)
|
||||
ESP_CMD_STREAM_CLOSE = 0x27, // (no parameters)
|
||||
|
||||
// VFS bridge: a small generic pipe, not one opcode per filesystem
|
||||
// operation - the actual op (VFS_OP_STAT/READDIR/OPEN/READ/WRITE/CLOSE/
|
||||
// DELETE/MKDIR/RMDIR) travels inside the request payload, mirroring how
|
||||
// the fake filesystem's own Vfs_Dispatch works. The Z80 side polls
|
||||
// ESP_CMD_VFS_POLL on every idle tick; when a request is pending it
|
||||
// fetches the payload, does the real filesystem op locally through WC,
|
||||
// and posts the result back.
|
||||
ESP_CMD_VFS_SERVER_START = 0x28, // i: ESP_REG_STRING_SIZE, ESP_REG_STRING_DATA (share name)
|
||||
ESP_CMD_VFS_SERVER_STOP = 0x29, // (no parameters)
|
||||
ESP_CMD_VFS_POLL = 0x2A, // o: ESP_REG_VFS_PENDING, ESP_REG_VFS_OP, ESP_REG_DATA_SIZE (request payload length, if pending)
|
||||
ESP_CMD_VFS_GET_REQUEST_DATA = 0x2B, // o: ESP_REG_DATA_SIZE (DMA read like ESP_CMD_READ_OBJECT)
|
||||
ESP_CMD_VFS_SUBMIT_RESULT = 0x2C, // i: ESP_REG_VFS_STATUS, ESP_REG_DATA_SIZE (DMA write like ESP_CMD_WRITE_OBJECT)
|
||||
|
||||
ESP_CMD_WEATHER_GET = 0x2D, // i: OBJ_HANDLE -> country\0zip\0 (DATA/DATAF)
|
||||
// o: new OBJ_HANDLE, DATA_OFFSET=0, DATA_SIZE=90
|
||||
|
||||
ESP_CMD_TRACK_INIT = 0xA0, // i: ESP_REG_OBJ_HANDLE
|
||||
ESP_CMD_TRACK_PLAY = 0xA1, // i: ESP_REG_OBJ_HANDLE
|
||||
ESP_CMD_TRACK_STOP = 0xA2, // (no parameters)
|
||||
ESP_CMD_TRACK_RESET = 0xA3, // i: ESP_REG_OBJ_HANDLE, stop playback and reset XMC context to start
|
||||
ESP_CMD_TRACK_SET_VOLUME = 0xA4, // +++
|
||||
ESP_CMD_TRACK_SET_S_RATE = 0xA5, // +++
|
||||
ESP_CMD_TRACK_SET_PARAMS = 0xA6, // i: ESP_REG_OBJ_HANDLE, ESP_REG_TRACK_MAX_LOOP_COUNT
|
||||
// (0 = loop forever, the default; N = stop playback after N loops)
|
||||
ESP_CMD_TRACK_SET_POS = 0xA7, // +++
|
||||
ESP_CMD_TRACK_GET_INFO = 0xA8, // i: ESP_REG_OBJ_HANDLE
|
||||
// o: ESP_REG_TRACK_STATUS, ESP_REG_TRACK_IS_CURRENT,
|
||||
// ESP_REG_TRACK_PATTERN_INDEX, ESP_REG_TRACK_PATTERN, ESP_REG_TRACK_ROW,
|
||||
// ESP_REG_TRACK_SAMPLES, ESP_REG_TRACK_BPM, ESP_REG_TRACK_TEMPO,
|
||||
// ESP_REG_TRACK_LOOP_COUNT, ESP_REG_TRACK_TOTAL_SAMPLES
|
||||
// (position/speed reflect the object's own context - live if
|
||||
// ESP_REG_TRACK_IS_CURRENT is set, otherwise a frozen snapshot from
|
||||
// when it last played or was reset)
|
||||
ESP_CMD_TRACK_GET_STATE = 0xA9, // i: ESP_REG_OBJ_HANDLE
|
||||
// o: ESP_REG_TRACK_STATUS, ESP_REG_TRACK_IS_CURRENT
|
||||
ESP_CMD_TRACK_GET_STATS = 0xAA, // +++
|
||||
|
||||
ESP_CMD_XM_STREAM_LOAD = 0xAB, // i: ESP_REG_DATA_SIZE (tx chunk, aligned), ESP_REG_DATA_OFFSET (total on first chunk, 0 on next chunks)
|
||||
// o: ESP_REG_DATA_OFFSET (chunk offset), ESP_REG_DATA_SIZE (chunk size), ESP_REG_OBJ_HANDLE
|
||||
ESP_CMD_MOD_STREAM_LOAD = 0xAC, // i: ESP_REG_DATA_SIZE (tx chunk, aligned), ESP_REG_DATA_OFFSET (total on first chunk, 0 on next chunks)
|
||||
// o: ESP_REG_DATA_OFFSET (chunk offset), ESP_REG_DATA_SIZE (chunk size), ESP_REG_OBJ_HANDLE
|
||||
ESP_CMD_S3M_STREAM_LOAD = 0xAD, // i: ESP_REG_DATA_SIZE (tx chunk, aligned), ESP_REG_DATA_OFFSET (total on first chunk, 0 on next chunks)
|
||||
// o: ESP_REG_DATA_OFFSET (chunk offset), ESP_REG_DATA_SIZE (chunk size), ESP_REG_OBJ_HANDLE
|
||||
|
||||
ESP_CMD_SFX_PLAY = 0xB0, // i: ESP_REG_OBJ_HANDLE, ESP_REG_SFX_GROUP; o: ESP_REG_SFX_CHANNEL
|
||||
ESP_CMD_SFX_PLAY_EX = 0xB1, // i: ESP_REG_OBJ_HANDLE, ESP_REG_SFX_GROUP, ESP_REG_SFX_VOLUME, ESP_REG_SFX_PAN, ESP_REG_SFX_PITCH; o: ESP_REG_SFX_CHANNEL
|
||||
ESP_CMD_SFX_STOP = 0xB2, // i: ESP_REG_SFX_CHANNEL
|
||||
ESP_CMD_SFX_SET_PARAMS = 0xB3, // i: ESP_REG_SFX_CHANNEL, ESP_REG_SFX_VOLUME, ESP_REG_SFX_PAN, ESP_REG_SFX_PITCH
|
||||
ESP_CMD_SFX_SET_VOLUME = 0xB4, // i: ESP_REG_SFX_VOLUME
|
||||
ESP_CMD_SFX_GET_STATE = 0xB5, // i: ESP_REG_SFX_CHANNEL; o: ESP_REG_SFX_STATE_*
|
||||
ESP_CMD_SFX_STOP_GROUP = 0xB6, // i: ESP_REG_SFX_GROUP
|
||||
|
||||
ESP_CMD_CONFIG_SPI = 0xC0, // +++
|
||||
|
||||
ESP_CMD_LOAD_ELF = 0xD0, // i: ESP_REG_OBJ_HANDLE
|
||||
// o: ESP_REG_LIB_HANDLE
|
||||
ESP_CMD_LOAD_ELF_OPT = 0xD1, // i: ESP_REG_OBJ_HANDLE, ESP_REG_OPT
|
||||
// o: ESP_REG_LIB_HANDLE
|
||||
ESP_CMD_RUN_FUNC0 = 0xD2, // i: ESP_REG_LIB_HANDLE, ESP_REG_FUNC, ESP_REG_ARG,
|
||||
// o: ESP_REG_RETVAL
|
||||
ESP_CMD_RUN_FUNC1 = 0xD3, // i: ESP_REG_LIB_HANDLE, ESP_REG_FUNC, ESP_REG_ARG, ESP_REG_ARR1_HANDLE
|
||||
// o: ESP_REG_RETVAL
|
||||
ESP_CMD_RUN_FUNC2 = 0xD4, // i: ESP_REG_LIB_HANDLE, ESP_REG_FUNC, ESP_REG_ARG, ESP_REG_ARR1_HANDLE, ESP_REG_ARR2_HANDLE
|
||||
// o: ESP_REG_RETVAL
|
||||
ESP_CMD_RUN_FUNC3 = 0xD5, // i: ESP_REG_LIB_HANDLE, ESP_REG_FUNC, ESP_REG_ARG, ESP_REG_ARR1_HANDLE, ESP_REG_ARR2_HANDLE, ESP_REG_ARR3_HANDLE
|
||||
// o: ESP_REG_RETVAL
|
||||
|
||||
ESP_CMD_MAKE_OBJECT = 0xE0, // i: ESP_REG_DATA_SIZE, ESP_REG_OBJ_TYPE
|
||||
// o: ESP_REG_OBJ_HANDLE, ESP_REG_DATA_OFFSET, ESP_REG_DATA_SIZE
|
||||
ESP_CMD_WRITE_OBJECT = 0xE1, // i: ESP_REG_OBJ_HANDLE, ESP_REG_DATA_OFFSET, ESP_REG_DATA_SIZE
|
||||
// o: ESP_REG_DATA_OFFSET, ESP_REG_DATA_SIZE
|
||||
ESP_CMD_READ_OBJECT = 0xE2, // i: ESP_REG_OBJ_HANDLE, ESP_REG_DATA_OFFSET, ESP_REG_DATA_SIZE
|
||||
// o: ESP_REG_DATA_OFFSET, ESP_REG_DATA_SIZE
|
||||
ESP_CMD_DELETE_OBJECT = 0xE3, // i: ESP_REG_OBJ_HANDLE
|
||||
ESP_CMD_KILL_OBJECTS = 0xE4, // (no parameters)
|
||||
ESP_CMD_FILL_OBJECT = 0xE5, // +++
|
||||
ESP_CMD_COPY_OBJECT = 0xE6, // +++
|
||||
ESP_CMD_GET_OBJECT_INFO = 0xE7, // +++
|
||||
ESP_CMD_REBOOT = 0xED, // (no parameters)
|
||||
ESP_CMD_RESET = 0xEE, // (no parameters)
|
||||
ESP_CMD_BREAK = 0xEF, // abort active host tracker stream state, READY if idle
|
||||
|
||||
ESP_CMD_GET_RND = 0xF0, // i: ESP_REG_DATA_SIZE
|
||||
ESP_CMD_DEHST = 0xF1, // i: ESP_REG_OBJ_HANDLE, ESP_REG_DATA_SIZE, ESP_REG_OBJ_TYPE
|
||||
// o: ESP_REG_DATA_OFFSET, ESP_REG_DATA_SIZE
|
||||
ESP_CMD_UNZIP = 0xF2, // i: ESP_REG_OBJ_HANDLE, ESP_REG_DATA_SIZE, ESP_REG_OBJ_TYPE
|
||||
// o: ESP_REG_DATA_OFFSET, ESP_REG_DATA_SIZE
|
||||
ESP_CMD_STREAM_UNZIP = 0xF3, // i: ESP_REG_OBJ_TYPE
|
||||
// o: ESP_REG_OBJ_HANDLE
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
ESP_OPT_DATA_SRAM = 0x01,
|
||||
ESP_OPT_RODATA_SRAM = 0x02,
|
||||
ESP_OPT_BSS_SRAM = 0x04,
|
||||
|
||||
ESP_OPT_SPI_1BIT = 0x00,
|
||||
ESP_OPT_SPI_2BIT = 0x40,
|
||||
ESP_OPT_SPI_4BIT = 0x80,
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
// Status codes
|
||||
ESP_ST_IDLE = 0x00, // Idle
|
||||
ESP_ST_READY = 0x01, // Command completed
|
||||
ESP_ST_BUSY = 0x02, // Busy executing command or initializing
|
||||
ESP_ST_DATA_M2S = 0x03, // Ready to receive data from Master to Slave
|
||||
ESP_ST_DATA_S2M = 0x04, // Ready to send data from Slave to Master
|
||||
|
||||
ESP_ST_RESET = 0x7E, // Reset performed, ready
|
||||
|
||||
// Error codes
|
||||
ESP_ERR_INV_COMMAND = 0x80,
|
||||
ESP_ERR_INV_PARAM ,
|
||||
ESP_ERR_INV_STATE ,
|
||||
ESP_ERR_INV_STR_LEN ,
|
||||
ESP_ERR_INV_SIZE , // 84
|
||||
ESP_ERR_INV_OBJ_TYPE ,
|
||||
ESP_ERR_INV_ARR_NUM ,
|
||||
ESP_ERR_INV_HANDLE ,
|
||||
ESP_ERR_INV_XM_HANDLE , // 88
|
||||
ESP_ERR_INV_LIB_HANDLE ,
|
||||
ESP_ERR_INV_ELF_HANDLE ,
|
||||
ESP_ERR_INV_HST_HANDLE ,
|
||||
ESP_ERR_INV_BSS_HANDLE , // 8C
|
||||
ESP_ERR_INV_ARG_HANDLE ,
|
||||
ESP_ERR_INV_SRC_HANDLE ,
|
||||
ESP_ERR_INV_DST_HANDLE ,
|
||||
ESP_ERR_INV_XM , // 90,
|
||||
ESP_ERR_INV_LIB ,
|
||||
ESP_ERR_INV_ELF ,
|
||||
ESP_ERR_INV_HST ,
|
||||
ESP_ERR_INV_ZIP , // 94
|
||||
ESP_ERR_INV_MOD ,
|
||||
ESP_ERR_INV_MOD_HANDLE ,
|
||||
ESP_ERR_INV_S3M ,
|
||||
ESP_ERR_INV_S3M_HANDLE , // 98
|
||||
ESP_ERR_OUT_OF_MEMORY ,
|
||||
ESP_ERR_OUT_OF_HANDLES ,
|
||||
ESP_ERR_OBJ_NOT_DELETED ,
|
||||
|
||||
ESP_ERR_AP_NOT_CONNECTED = 0xB0,
|
||||
ESP_ERR_NET_BUSY ,
|
||||
ESP_ERR_WEATHER = 0xB5, // weather HTTP/JSON failure; details in log
|
||||
|
||||
ESP_ERR_VFS_NO_REQUEST = 0x9C, // ESP_CMD_VFS_GET_REQUEST_DATA/SUBMIT_RESULT with nothing pending
|
||||
ESP_ERR_VFS_TIMEOUT = 0x9D, // no VFS client waiting for a result (e.g. it gave up)
|
||||
};
|
||||
|
||||
// VFS bridge request opcodes - travel inside the ESP_CMD_VFS_GET_REQUEST_DATA
|
||||
// payload, not as separate ESP_CMD_* values (see ESP_CMD_VFS_POLL comment).
|
||||
enum
|
||||
{
|
||||
VFS_OP_STAT = 0x40,
|
||||
VFS_OP_READDIR = 0x41,
|
||||
VFS_OP_OPEN = 0x42,
|
||||
VFS_OP_READ = 0x43,
|
||||
VFS_OP_WRITE = 0x44,
|
||||
VFS_OP_CLOSE = 0x45,
|
||||
VFS_OP_DELETE = 0x46,
|
||||
VFS_OP_MKDIR = 0x47,
|
||||
VFS_OP_RMDIR = 0x48,
|
||||
// 2026-09-08: real filesystem-size/free-space query, via Wild Commander
|
||||
// Improved's FILEX_OP_GET_FS_INFO (needs FILEX.WMF installed) - see
|
||||
// do_statfs() in smbvfs/src/main.c. No request payload. Result: u32
|
||||
// total_clusters, u32 free_clusters, u8 sectors_per_cluster, u16
|
||||
// bytes_per_sector (11 bytes) - status=0 on success. Was previously
|
||||
// faked as a hardcoded 512MB/32MB in vfs_bridge.cpp, see
|
||||
// project_smbvfs_plugin.md.
|
||||
VFS_OP_STATFS = 0x49,
|
||||
};
|
||||
|
||||
// Info type (ESP_CMD_GET_INFO_STR)
|
||||
enum
|
||||
{
|
||||
GET_INFO_COPYRIGHT = 0x00,
|
||||
GET_INFO_API = 0x01,
|
||||
GET_INFO_BUILD = 0x02,
|
||||
GET_INFO_IDF = 0x03,
|
||||
};
|
||||
|
||||
// Memory object types (add new types to mem_obj.cpp)
|
||||
enum
|
||||
{
|
||||
OBJ_TYPE_NONE = 0,
|
||||
OBJ_TYPE_DATA = 1, // SPIRAM
|
||||
OBJ_TYPE_DATAF = 2, // SRAM
|
||||
OBJ_TYPE_ELF = 3, // SPIRAM
|
||||
OBJ_TYPE_LIB = 4, // Virtual
|
||||
OBJ_TYPE_XM = 5, // SPIRAM
|
||||
OBJ_TYPE_WAV = 6, // SPIRAM
|
||||
OBJ_TYPE_HST = 7, // SPIRAM
|
||||
OBJ_TYPE_ZIP = 8, // SPIRAM
|
||||
OBJ_TYPE_XMC = 9, // SPIRAM
|
||||
OBJ_TYPE_MOD = 10, // SPIRAM
|
||||
OBJ_TYPE_MDC = 11, // SPIRAM
|
||||
OBJ_TYPE_S3M = 12, // SPIRAM
|
||||
OBJ_TYPE_S3C = 13, // SPIRAM
|
||||
OBJ_TYPE_MBC = 14, // SPIRAM
|
||||
};
|
||||
|
||||
// Network state
|
||||
enum
|
||||
{
|
||||
NETWORK_CLOSED = 0x00,
|
||||
NETWORK_OPENING = 0x01,
|
||||
NETWORK_OPEN = 0x02,
|
||||
NETWORK_CLOSING = 0x03,
|
||||
NETWORK_UNKNOWN = 0xFF
|
||||
};
|
||||
|
||||
// SPI Slave Commands (ESP32)
|
||||
enum
|
||||
{
|
||||
ESP_SPI_CMD_WR_REGS = 0x01,
|
||||
ESP_SPI_CMD_RD_REGS = 0x02,
|
||||
ESP_SPI_CMD_WR_DATA = 0x03,
|
||||
ESP_SPI_CMD_RD_DATA = 0x04,
|
||||
ESP_SPI_CMD_W_END = 0x07,
|
||||
ESP_SPI_CMD_R_END = 0x08,
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
OBJ_ST_NONE = 0,
|
||||
|
||||
LIB_OBJ_ST_READY = 0x10, // Library loaded
|
||||
|
||||
TRACK_OBJ_ST_STOPPED = 0x20, // Tracker object context created, idling
|
||||
TRACK_OBJ_ST_PLAYING = 0x21, // Tracker object playing
|
||||
|
||||
WAV_OBJ_ST_PLAYING = 0x30, // WAV object is used by SFX renderer
|
||||
|
||||
OBJ_ST_DELETING = 0xFE, // Object is being deleted, new users must not attach
|
||||
OBJ_ST_ERROR = 0xFF // Reserved
|
||||
};
|
||||
@@ -0,0 +1,618 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include "esp_console.h"
|
||||
#include "esp_partition.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "diskio_impl.h"
|
||||
#include "diskio_wl.h"
|
||||
#include "ff.h"
|
||||
#include "wear_levelling.h"
|
||||
#include "fatfs.h"
|
||||
|
||||
bool fs_initialized = false;
|
||||
bool fs_fs_mounted = false;
|
||||
char fs_fs_base_path[32] = { 0 };
|
||||
char fs_part_label[16] = { 0 };
|
||||
wl_handle_t fs_wl_handle = WL_INVALID_HANDLE;
|
||||
FATFS *fs_fs_fatfs = NULL;
|
||||
BYTE fs_fs_pdrv = FF_DRV_NOT_USED;
|
||||
bool fs_fs_vfs_registered = false;
|
||||
bool fs_fs_diskio_registered = false;
|
||||
bool fs_fs_fat_mounted = false;
|
||||
bool fs_fs_no_filesystem = false;
|
||||
FRESULT fs_fs_last_mount_result = FR_OK;
|
||||
|
||||
#define FS_VFS_MAX_FILES 4
|
||||
|
||||
int fs_build_full_path(const char *base_path, const char *path, char *full, size_t full_size)
|
||||
{
|
||||
int n;
|
||||
|
||||
if (!base_path || !base_path[0] || !full || full_size < 2) return 0;
|
||||
|
||||
if (!path || !path[0] || strcmp(path, "/") == 0)
|
||||
{
|
||||
n = snprintf(full, full_size, "%s", base_path);
|
||||
return n >= 0 && (size_t)n < full_size;
|
||||
}
|
||||
|
||||
if (path[0] == '/')
|
||||
{
|
||||
n = snprintf(full, full_size, "%s%s", base_path, path);
|
||||
return n >= 0 && (size_t)n < full_size;
|
||||
}
|
||||
|
||||
n = snprintf(full, full_size, "%s/%s", base_path, path);
|
||||
return n >= 0 && (size_t)n < full_size;
|
||||
}
|
||||
|
||||
const esp_partition_t *fs_find_partition()
|
||||
{
|
||||
return esp_partition_find_first(
|
||||
ESP_PARTITION_TYPE_DATA,
|
||||
ESP_PARTITION_SUBTYPE_DATA_FAT,
|
||||
FS_PART_LABEL);
|
||||
}
|
||||
|
||||
void fs_clear_mount_state()
|
||||
{
|
||||
fs_initialized = false;
|
||||
fs_fs_mounted = false;
|
||||
fs_fs_fatfs = NULL;
|
||||
fs_fs_pdrv = FF_DRV_NOT_USED;
|
||||
fs_fs_vfs_registered = false;
|
||||
fs_fs_diskio_registered = false;
|
||||
fs_fs_fat_mounted = false;
|
||||
fs_fs_base_path[0] = 0;
|
||||
fs_part_label[0] = 0;
|
||||
fs_wl_handle = WL_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
void fs_unmount_force()
|
||||
{
|
||||
char drv[3];
|
||||
|
||||
if (fs_fs_pdrv != FF_DRV_NOT_USED)
|
||||
{
|
||||
drv[0] = (char)('0' + fs_fs_pdrv);
|
||||
drv[1] = ':';
|
||||
drv[2] = 0;
|
||||
|
||||
if (fs_fs_fat_mounted)
|
||||
f_mount(NULL, drv, 0);
|
||||
}
|
||||
|
||||
if (fs_fs_diskio_registered && fs_fs_pdrv != FF_DRV_NOT_USED)
|
||||
{
|
||||
ff_diskio_unregister(fs_fs_pdrv);
|
||||
if (fs_wl_handle != WL_INVALID_HANDLE)
|
||||
ff_diskio_clear_pdrv_wl(fs_wl_handle);
|
||||
}
|
||||
|
||||
if (fs_fs_vfs_registered)
|
||||
esp_vfs_fat_unregister_path(fs_fs_base_path[0] ? fs_fs_base_path : FS_BASE_PATH);
|
||||
|
||||
if (fs_wl_handle != WL_INVALID_HANDLE)
|
||||
wl_unmount(fs_wl_handle);
|
||||
|
||||
fs_clear_mount_state();
|
||||
}
|
||||
|
||||
esp_err_t fs_register_fat(const esp_partition_t *part, const esp_vfs_fat_mount_config_t *mount_cfg)
|
||||
{
|
||||
char drv[3];
|
||||
esp_err_t err;
|
||||
|
||||
if (!part || !mount_cfg) return ESP_ERR_INVALID_ARG;
|
||||
|
||||
err = wl_mount(part, &fs_wl_handle);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
err = ff_diskio_get_drive(&fs_fs_pdrv);
|
||||
if (err != ESP_OK || fs_fs_pdrv == FF_DRV_NOT_USED)
|
||||
return ESP_ERR_NO_MEM;
|
||||
|
||||
err = ff_diskio_register_wl_partition(fs_fs_pdrv, fs_wl_handle);
|
||||
if (err != ESP_OK) return err;
|
||||
fs_fs_diskio_registered = true;
|
||||
|
||||
drv[0] = (char)('0' + fs_fs_pdrv);
|
||||
drv[1] = ':';
|
||||
drv[2] = 0;
|
||||
|
||||
esp_vfs_fat_conf_t fs_fatfs_conf = {
|
||||
.base_path = FS_BASE_PATH,
|
||||
.fat_drive = drv,
|
||||
.max_files = (size_t)mount_cfg->max_files,
|
||||
};
|
||||
err = esp_vfs_fat_register(&fs_fatfs_conf, &fs_fs_fatfs);
|
||||
if (err != ESP_OK) return err;
|
||||
fs_fs_vfs_registered = true;
|
||||
|
||||
fs_fs_last_mount_result = f_mount(fs_fs_fatfs, drv, 1);
|
||||
if (fs_fs_last_mount_result != FR_OK)
|
||||
{
|
||||
fs_fs_no_filesystem = fs_fs_last_mount_result == FR_NO_FILESYSTEM;
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
fs_fs_fat_mounted = true;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
size_t fs_get_allocation_unit_size(size_t part_size)
|
||||
{
|
||||
if (part_size >= (16u * 1024u * 1024u)) return 16u * 1024u;
|
||||
if (part_size >= (4u * 1024u * 1024u)) return 4u * 1024u;
|
||||
return 4096;
|
||||
}
|
||||
|
||||
void fs_fill_mount_cfg(esp_vfs_fat_mount_config_t *cfg, bool format_if_mount_failed)
|
||||
{
|
||||
const esp_partition_t *part = fs_find_partition();
|
||||
size_t part_size = part ? part->size : 0;
|
||||
|
||||
memset(cfg, 0, sizeof(*cfg));
|
||||
cfg->format_if_mount_failed = format_if_mount_failed;
|
||||
cfg->max_files = FS_VFS_MAX_FILES;
|
||||
cfg->allocation_unit_size = fs_get_allocation_unit_size(part_size);
|
||||
cfg->disk_status_check_enable = false;
|
||||
cfg->use_one_fat = true;
|
||||
}
|
||||
|
||||
|
||||
const char *fs_fat_type_name(BYTE fs_type)
|
||||
{
|
||||
switch (fs_type)
|
||||
{
|
||||
case FS_FAT12: return "FAT12";
|
||||
case FS_FAT16: return "FAT16";
|
||||
case FS_FAT32: return "FAT32";
|
||||
#if defined(FF_FS_EXFAT) && FF_FS_EXFAT
|
||||
case FS_EXFAT: return "exFAT";
|
||||
#endif
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t fs_fat_sector_size(FATFS *fat)
|
||||
{
|
||||
if (!fat) return 0;
|
||||
|
||||
#if FF_MAX_SS != FF_MIN_SS
|
||||
return fat->ssize;
|
||||
#else
|
||||
return FF_MIN_SS;
|
||||
#endif
|
||||
}
|
||||
|
||||
FRESULT fs_get_mounted_fatfs(FATFS **out_fat, DWORD *out_free_clusters)
|
||||
{
|
||||
if (!out_fat || !out_free_clusters) return FR_INVALID_PARAMETER;
|
||||
|
||||
*out_fat = NULL;
|
||||
*out_free_clusters = 0;
|
||||
|
||||
FRESULT res = f_getfree("", out_free_clusters, out_fat);
|
||||
if (res == FR_OK) return res;
|
||||
|
||||
return f_getfree("0:", out_free_clusters, out_fat);
|
||||
}
|
||||
|
||||
void fs_print_fatfs_details(uint64_t total_bytes, uint64_t free_bytes, esp_err_t usage_err)
|
||||
{
|
||||
FATFS *fat = NULL;
|
||||
DWORD free_clusters_fatfs = 0;
|
||||
FRESULT res = fs_get_mounted_fatfs(&fat, &free_clusters_fatfs);
|
||||
|
||||
if (res != FR_OK || !fat)
|
||||
{
|
||||
printf(" fatfs : unavailable (FRESULT=%u)\r\n", (unsigned)res);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t sector_size = fs_fat_sector_size(fat);
|
||||
uint32_t cluster_size = (uint32_t)fat->csize * sector_size;
|
||||
uint32_t total_clusters = fat->n_fatent >= 2 ? fat->n_fatent - 2 : 0;
|
||||
uint32_t free_clusters = (uint32_t)free_clusters_fatfs;
|
||||
|
||||
if (free_clusters > total_clusters)
|
||||
free_clusters = total_clusters;
|
||||
|
||||
uint32_t used_clusters = total_clusters >= free_clusters ? total_clusters - free_clusters : 0;
|
||||
uint64_t data_bytes = (uint64_t)total_clusters * cluster_size;
|
||||
uint64_t fat_bytes = (uint64_t)fat->fsize * sector_size;
|
||||
|
||||
printf(" fat type : %s (%u)\r\n", fs_fat_type_name(fat->fs_type), (unsigned)fat->fs_type);
|
||||
printf(" fat copies : %u\r\n", (unsigned)fat->n_fats);
|
||||
printf(" sector : %u bytes\r\n", (unsigned)sector_size);
|
||||
printf(" cluster : %u sectors, %u bytes\r\n", (unsigned)fat->csize, (unsigned)cluster_size);
|
||||
printf(" clusters : total=%u used=%u free=%u\r\n", (unsigned)total_clusters, (unsigned)used_clusters, (unsigned)free_clusters);
|
||||
printf(" data area : %llu bytes\r\n", (unsigned long long)data_bytes);
|
||||
printf(" fat size : %u sectors, %llu bytes each\r\n", (unsigned)fat->fsize, (unsigned long long)fat_bytes);
|
||||
printf(" root ents : %u\r\n", (unsigned)fat->n_rootdir);
|
||||
printf(" vol base : %llu\r\n", (unsigned long long)fat->volbase);
|
||||
printf(" fat base : %llu\r\n", (unsigned long long)fat->fatbase);
|
||||
printf(" dir base : %llu\r\n", (unsigned long long)fat->dirbase);
|
||||
printf(" data base : %llu\r\n", (unsigned long long)fat->database);
|
||||
|
||||
if (usage_err == ESP_OK && total_bytes != data_bytes)
|
||||
printf(" vfs total : %llu bytes\r\n", (unsigned long long)total_bytes);
|
||||
|
||||
if (usage_err == ESP_OK && cluster_size > 0 && free_bytes != (uint64_t)free_clusters * cluster_size)
|
||||
printf(" vfs free : %llu bytes\r\n", (unsigned long long)free_bytes);
|
||||
}
|
||||
|
||||
esp_err_t fs_mount_impl(bool quiet)
|
||||
{
|
||||
const esp_partition_t *part;
|
||||
esp_err_t err;
|
||||
|
||||
if (fs_fs_mounted)
|
||||
{
|
||||
fs_initialized = true;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
fs_fs_no_filesystem = false;
|
||||
fs_fs_last_mount_result = FR_OK;
|
||||
part = fs_find_partition();
|
||||
if (!part) return ESP_ERR_NOT_FOUND;
|
||||
|
||||
esp_vfs_fat_mount_config_t mount_cfg;
|
||||
fs_fill_mount_cfg(&mount_cfg, false);
|
||||
|
||||
err = fs_register_fat(part, &mount_cfg);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
fs_unmount_force();
|
||||
|
||||
if (fs_fs_no_filesystem)
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
|
||||
if (!quiet)
|
||||
{
|
||||
if (fs_fs_last_mount_result != FR_OK)
|
||||
printf("E: FAT f_mount failed: FRESULT=%u\r\n", (unsigned)fs_fs_last_mount_result);
|
||||
else
|
||||
printf("E: FAT mount failed: %s\r\n", esp_err_to_name(err));
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
fs_initialized = true;
|
||||
fs_fs_mounted = true;
|
||||
fs_fs_no_filesystem = false;
|
||||
fs_fs_last_mount_result = FR_OK;
|
||||
snprintf(fs_fs_base_path, sizeof(fs_fs_base_path), "%s", FS_BASE_PATH);
|
||||
snprintf(fs_part_label, sizeof(fs_part_label), "%s", FS_PART_LABEL);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t fs_mount()
|
||||
{
|
||||
return fs_mount_impl(false);
|
||||
}
|
||||
|
||||
esp_err_t fs_mount_quiet()
|
||||
{
|
||||
return fs_mount_impl(true);
|
||||
}
|
||||
|
||||
esp_err_t fs_format()
|
||||
{
|
||||
const esp_partition_t *part = fs_find_partition();
|
||||
if (!part) return ESP_ERR_NOT_FOUND;
|
||||
|
||||
if (fs_fs_mounted)
|
||||
fs_deinit();
|
||||
|
||||
esp_vfs_fat_mount_config_t format_cfg;
|
||||
fs_fill_mount_cfg(&format_cfg, true);
|
||||
|
||||
esp_err_t err = esp_vfs_fat_spiflash_format_cfg_rw_wl(
|
||||
FS_BASE_PATH,
|
||||
FS_PART_LABEL,
|
||||
&format_cfg);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
printf("E: esp_vfs_fat_spiflash_format_cfg_rw_wl failed: %s\r\n", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
return fs_mount();
|
||||
}
|
||||
|
||||
esp_err_t fs_delete()
|
||||
{
|
||||
const esp_partition_t *part = fs_find_partition();
|
||||
|
||||
if (!part) return ESP_ERR_NOT_FOUND;
|
||||
|
||||
fs_deinit();
|
||||
esp_err_t err = esp_partition_erase_range(part, 0, part->size);
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
fs_fs_no_filesystem = true;
|
||||
fs_fs_last_mount_result = FR_NO_FILESYSTEM;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
void fs_deinit()
|
||||
{
|
||||
fs_unmount_force();
|
||||
fs_fs_no_filesystem = false;
|
||||
fs_fs_last_mount_result = FR_OK;
|
||||
}
|
||||
|
||||
esp_err_t fs_ensure_ready()
|
||||
{
|
||||
return fs_mount();
|
||||
}
|
||||
|
||||
esp_err_t fs_ensure_ready_quiet()
|
||||
{
|
||||
return fs_mount_quiet();
|
||||
}
|
||||
|
||||
esp_err_t fs_list_dir(const char *base_path, const char *path)
|
||||
{
|
||||
char full[256];
|
||||
|
||||
if (!fs_build_full_path(base_path, path, full, sizeof(full)))
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
|
||||
errno = 0;
|
||||
DIR *d = opendir(full);
|
||||
if (!d)
|
||||
{
|
||||
int saved_errno = errno;
|
||||
printf("E: opendir('%s') failed, errno=%d\r\n", full, saved_errno);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
printf("Listing: %s\r\n", full);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
struct dirent *e = readdir(d);
|
||||
if (!e) break;
|
||||
|
||||
if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0)
|
||||
continue;
|
||||
|
||||
char p[256];
|
||||
int n = snprintf(p, sizeof(p), "%s/%s", full, e->d_name);
|
||||
if (n < 0 || (size_t)n >= sizeof(p))
|
||||
{
|
||||
printf(" ? ? %s (path too long)\r\n", e->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
struct stat st = {};
|
||||
if (stat(p, &st) == 0)
|
||||
{
|
||||
const char *t = S_ISDIR(st.st_mode) ? "DIR " : "FILE";
|
||||
printf(" %s %10" PRIu32 " %s\r\n", t, (uint32_t)st.st_size, e->d_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(" ? ? %s\r\n", e->d_name);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(d);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
int fs_info(int argc, char **argv)
|
||||
{
|
||||
const esp_partition_t *part;
|
||||
uint64_t total = 0;
|
||||
uint64_t free = 0;
|
||||
uint64_t used = 0;
|
||||
esp_err_t err;
|
||||
|
||||
if (argc < 0 || !argv) return 1;
|
||||
|
||||
err = fs_ensure_ready_quiet();
|
||||
part = fs_find_partition();
|
||||
|
||||
printf("FAT info\r\n");
|
||||
printf(" label : %s\r\n", FS_PART_LABEL);
|
||||
printf(" base path : %s\r\n", FS_BASE_PATH);
|
||||
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
if (err == ESP_ERR_NOT_FOUND)
|
||||
{
|
||||
printf(" state : partition not found\r\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (fs_fs_no_filesystem)
|
||||
{
|
||||
printf(" state : no FAT filesystem\r\n");
|
||||
if (part)
|
||||
{
|
||||
printf(" address : 0x%08" PRIX32 "\r\n", part->address);
|
||||
printf(" size : 0x%08lx (%lu bytes)\r\n", (unsigned long)part->size, (unsigned long)part->size);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (fs_fs_last_mount_result != FR_OK)
|
||||
printf(" state : unavailable (FRESULT=%u)\r\n", (unsigned)fs_fs_last_mount_result);
|
||||
else
|
||||
printf(" state : unavailable (%s)\r\n", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
err = esp_vfs_fat_info(FS_BASE_PATH, &total, &free);
|
||||
if (err == ESP_OK)
|
||||
used = total - free;
|
||||
|
||||
printf(" mounted : %s\r\n", fs_fs_mounted ? "yes" : "no");
|
||||
|
||||
esp_vfs_fat_mount_config_t cfg;
|
||||
fs_fill_mount_cfg(&cfg, false);
|
||||
printf(" cfg alloc : %u bytes\r\n", (unsigned)cfg.allocation_unit_size);
|
||||
printf(" cfg fats : %u on next format\r\n", cfg.use_one_fat ? 1u : 2u);
|
||||
printf(" max files : %d\r\n", cfg.max_files);
|
||||
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
printf(" total : %llu bytes\r\n", (unsigned long long)total);
|
||||
printf(" used : %llu bytes\r\n", (unsigned long long)used);
|
||||
printf(" free : %llu bytes\r\n", (unsigned long long)free);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(" usage : unavailable (%s)\r\n", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
fs_print_fatfs_details(total, free, err);
|
||||
|
||||
if (part)
|
||||
{
|
||||
printf(" address : 0x%08" PRIX32 "\r\n", part->address);
|
||||
printf(" size : 0x%08lx (%lu bytes)\r\n", (unsigned long)part->size, (unsigned long)part->size);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(" partition : not found\r\n");
|
||||
}
|
||||
|
||||
if (fs_wl_handle != WL_INVALID_HANDLE)
|
||||
{
|
||||
size_t wl_bytes = wl_size(fs_wl_handle);
|
||||
size_t wl_sector = wl_sector_size(fs_wl_handle);
|
||||
printf(" wl size : %u bytes\r\n", (unsigned)wl_bytes);
|
||||
printf(" wl sector : %u bytes\r\n", (unsigned)wl_sector);
|
||||
printf(" wl handle : %d\r\n", (int)fs_wl_handle);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
esp_err_t fs_init()
|
||||
{
|
||||
return fs_format();
|
||||
}
|
||||
|
||||
int fs_format_cmd(int argc, char **argv)
|
||||
{
|
||||
if (argc < 0 || !argv) return 1;
|
||||
|
||||
const esp_partition_t *part = fs_find_partition();
|
||||
if (!part)
|
||||
{
|
||||
printf("E: partition '%s' not found\r\n", FS_PART_LABEL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
esp_vfs_fat_mount_config_t format_cfg;
|
||||
fs_fill_mount_cfg(&format_cfg, true);
|
||||
|
||||
esp_err_t err = fs_format();
|
||||
if (err != ESP_OK) return 1;
|
||||
|
||||
printf("FAT formatted and mounted: %s -> %s\r\n", FS_PART_LABEL, FS_BASE_PATH);
|
||||
printf(" address : 0x%08" PRIX32 "\r\n", part->address);
|
||||
printf(" size : 0x%08lx (%lu bytes)\r\n", (unsigned long)part->size, (unsigned long)part->size);
|
||||
printf(" alloc unit : %u bytes\r\n", (unsigned)format_cfg.allocation_unit_size);
|
||||
printf(" fat copies : %u\r\n", format_cfg.use_one_fat ? 1u : 2u);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fs_delete_cmd(int argc, char **argv)
|
||||
{
|
||||
const esp_partition_t *part;
|
||||
esp_err_t err;
|
||||
|
||||
if (argc < 0 || !argv) return 1;
|
||||
|
||||
part = fs_find_partition();
|
||||
if (!part)
|
||||
{
|
||||
printf("E: partition '%s' not found\r\n", FS_PART_LABEL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
err = fs_delete();
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
printf("E: FAT erase failed: %s\r\n", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("FAT erased: %s\r\n", FS_PART_LABEL);
|
||||
printf(" address : 0x%08" PRIX32 "\r\n", part->address);
|
||||
printf(" size : 0x%08lx (%lu bytes)\r\n", (unsigned long)part->size, (unsigned long)part->size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fs_ls(int argc, char **argv)
|
||||
{
|
||||
const char *path = "/";
|
||||
|
||||
if (argc >= 3)
|
||||
path = argv[2];
|
||||
|
||||
esp_err_t err = fs_ensure_ready();
|
||||
if (err != ESP_OK) return 1;
|
||||
|
||||
err = fs_list_dir(FS_BASE_PATH, path);
|
||||
return (err == ESP_OK) ? 0 : 1;
|
||||
}
|
||||
|
||||
int fs_cmd(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2 || !argv[1])
|
||||
{
|
||||
printf("Usage:\r\n");
|
||||
printf(" fat format\r\n");
|
||||
printf(" fat info\r\n");
|
||||
printf(" fat del\r\n");
|
||||
printf(" fat ls [path]\r\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *op = argv[1];
|
||||
|
||||
if (!strcmp(op, "format"))
|
||||
return fs_format_cmd(argc, argv);
|
||||
|
||||
if (!strcmp(op, "info"))
|
||||
return fs_info(argc, argv);
|
||||
|
||||
if (!strcmp(op, "del"))
|
||||
return fs_delete_cmd(argc, argv);
|
||||
|
||||
if (!strcmp(op, "ls"))
|
||||
return fs_ls(argc, argv);
|
||||
|
||||
printf("Unknown subcommand: %s\r\n", op);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void fat_console_register_system_commands()
|
||||
{
|
||||
const esp_console_cmd_t cmd =
|
||||
{
|
||||
.command = "fat",
|
||||
.help = "FATFS commands: format/info/del/ls",
|
||||
.hint = NULL,
|
||||
.func = &fs_cmd,
|
||||
.argtable = NULL
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "wear_levelling.h"
|
||||
|
||||
#define FS_PART_LABEL "fat"
|
||||
#define FS_BASE_PATH "/fs"
|
||||
|
||||
extern bool fs_initialized;
|
||||
extern bool fs_fs_mounted;
|
||||
extern char fs_fs_base_path[32];
|
||||
extern char fs_part_label[16];
|
||||
extern wl_handle_t fs_wl_handle;
|
||||
|
||||
esp_err_t fs_mount();
|
||||
esp_err_t fs_mount_quiet();
|
||||
esp_err_t fs_format();
|
||||
esp_err_t fs_delete();
|
||||
esp_err_t fs_init();
|
||||
esp_err_t fs_ensure_ready();
|
||||
esp_err_t fs_ensure_ready_quiet();
|
||||
void fs_deinit();
|
||||
esp_err_t fs_list_dir(const char *base_path, const char *path);
|
||||
int fs_build_full_path(const char *base_path, const char *path, char *full, size_t full_size);
|
||||
|
||||
int fs_info(int argc, char **argv);
|
||||
int fs_format_cmd(int argc, char **argv);
|
||||
int fs_delete_cmd(int argc, char **argv);
|
||||
int fs_ls(int argc, char **argv);
|
||||
int fs_cmd(int argc, char **argv);
|
||||
|
||||
void fat_console_register_system_commands();
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
const u8 code_866_fnt[] =
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x82, 0xaa, 0x82,
|
||||
0x82, 0xba, 0x92, 0x7c, 0x7c, 0xfe, 0xd6, 0xfe, 0xfe, 0xc6, 0xee, 0x7c,
|
||||
0x44, 0xee, 0xfe, 0xfe, 0x7c, 0x38, 0x10, 0x00, 0x10, 0x38, 0x7c, 0xfe,
|
||||
0x7c, 0x38, 0x10, 0x00, 0x38, 0x38, 0xd6, 0xfe, 0xd6, 0x10, 0x38, 0x00,
|
||||
0x10, 0x38, 0x7c, 0xfe, 0x7c, 0x10, 0x38, 0x00, 0x00, 0x00, 0x38, 0x7c,
|
||||
0x7c, 0x38, 0x00, 0x00, 0xfe, 0xfe, 0xc6, 0x82, 0x82, 0xc6, 0xfe, 0xfe,
|
||||
0x00, 0x00, 0x38, 0x44, 0x44, 0x38, 0x00, 0x00, 0xfe, 0xfe, 0xc6, 0xba,
|
||||
0xba, 0xc6, 0xfe, 0xfe, 0x00, 0x0e, 0x06, 0x7a, 0xd8, 0x88, 0xd8, 0x70,
|
||||
0x38, 0x6c, 0x44, 0x6c, 0x38, 0x10, 0x38, 0x10, 0x1e, 0x12, 0x1e, 0x10,
|
||||
0x10, 0x70, 0xe0, 0x40, 0x3e, 0x22, 0x3e, 0x22, 0x22, 0x2e, 0xe4, 0x40,
|
||||
0x92, 0x7c, 0x6c, 0xc6, 0x6c, 0x7c, 0x92, 0x00, 0x80, 0xe0, 0xf8, 0xfe,
|
||||
0xf8, 0xe0, 0x80, 0x00, 0x02, 0x0e, 0x3e, 0xfe, 0x3e, 0x0e, 0x02, 0x00,
|
||||
0x10, 0x38, 0x7c, 0x10, 0x10, 0x7c, 0x38, 0x10, 0x6c, 0x6c, 0x6c, 0x6c,
|
||||
0x6c, 0x00, 0x6c, 0x00, 0x7e, 0xb6, 0xb6, 0x76, 0x36, 0x36, 0x36, 0x00,
|
||||
0x3c, 0x60, 0x3c, 0x66, 0x3c, 0x06, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x7c, 0x7c, 0x7c, 0x00, 0x10, 0x38, 0x7c, 0x10, 0x7c, 0x38, 0x10, 0x7c,
|
||||
0x10, 0x38, 0x7c, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x10, 0x10, 0x10,
|
||||
0x7c, 0x38, 0x10, 0x00, 0x00, 0x08, 0x04, 0x7e, 0x04, 0x08, 0x00, 0x00,
|
||||
0x00, 0x10, 0x20, 0x7e, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40,
|
||||
0x40, 0x7e, 0x00, 0x00, 0x00, 0x28, 0x6c, 0xfe, 0x6c, 0x28, 0x00, 0x00,
|
||||
0x10, 0x10, 0x38, 0x38, 0x7c, 0x7c, 0xfe, 0x00, 0xfe, 0x7c, 0x7c, 0x38,
|
||||
0x38, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30, 0x00, 0x00, 0x6c, 0x24, 0x48,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x7e, 0x24, 0x24, 0x7e, 0x24, 0x00,
|
||||
0x18, 0x3e, 0x60, 0x3c, 0x06, 0x7c, 0x18, 0x00, 0x63, 0x66, 0x0c, 0x18,
|
||||
0x30, 0x66, 0xc6, 0x00, 0x00, 0x18, 0x34, 0x18, 0x2c, 0x66, 0x3a, 0x00,
|
||||
0x00, 0x18, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x06, 0x0c, 0x18, 0x18,
|
||||
0x18, 0x0c, 0x06, 0x00, 0x60, 0x30, 0x18, 0x18, 0x18, 0x30, 0x60, 0x00,
|
||||
0x00, 0x24, 0x18, 0x7e, 0x18, 0x24, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7e,
|
||||
0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x30,
|
||||
0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x30, 0x00, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x00,
|
||||
0x00, 0x3c, 0x66, 0x6e, 0x76, 0x66, 0x3c, 0x00, 0x00, 0x18, 0x38, 0x18,
|
||||
0x18, 0x18, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x06, 0x3c, 0x60, 0x7e, 0x00,
|
||||
0x00, 0x3c, 0x66, 0x0c, 0x06, 0x66, 0x3c, 0x00, 0x00, 0x66, 0x66, 0x66,
|
||||
0x7e, 0x06, 0x06, 0x00, 0x00, 0x7e, 0x60, 0x7c, 0x06, 0x66, 0x3c, 0x00,
|
||||
0x00, 0x3c, 0x60, 0x7c, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x7e, 0x06, 0x06,
|
||||
0x0c, 0x18, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x3c, 0x66, 0x66, 0x3c, 0x00,
|
||||
0x00, 0x3c, 0x66, 0x66, 0x3e, 0x06, 0x3c, 0x00, 0x00, 0x00, 0x30, 0x00,
|
||||
0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x10, 0x20,
|
||||
0x06, 0x0c, 0x18, 0x30, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x3c, 0x00,
|
||||
0x3c, 0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x0c, 0x18, 0x30, 0x60, 0x00,
|
||||
0x00, 0x3c, 0x66, 0x06, 0x1c, 0x00, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x5a,
|
||||
0x5e, 0x60, 0x3c, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x00,
|
||||
0x00, 0x7c, 0x66, 0x7c, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x3c, 0x66, 0x60,
|
||||
0x60, 0x66, 0x3c, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x00,
|
||||
0x00, 0x7e, 0x60, 0x78, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x7e, 0x60, 0x60,
|
||||
0x7c, 0x60, 0x60, 0x00, 0x00, 0x3c, 0x66, 0x60, 0x6e, 0x66, 0x3e, 0x00,
|
||||
0x00, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x18, 0x18,
|
||||
0x18, 0x18, 0x3c, 0x00, 0x00, 0x06, 0x06, 0x06, 0x06, 0x66, 0x3c, 0x00,
|
||||
0x00, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x66, 0x00, 0x00, 0x60, 0x60, 0x60,
|
||||
0x60, 0x60, 0x7e, 0x00, 0x00, 0x82, 0xc6, 0xee, 0xd6, 0xc6, 0xc6, 0x00,
|
||||
0x00, 0x46, 0x66, 0x76, 0x6e, 0x66, 0x62, 0x00, 0x00, 0x3c, 0x66, 0x66,
|
||||
0x66, 0x66, 0x3c, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x00,
|
||||
0x00, 0x3c, 0x66, 0x66, 0x66, 0x6e, 0x3c, 0x06, 0x00, 0x7c, 0x66, 0x66,
|
||||
0x7c, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x60, 0x3c, 0x06, 0x66, 0x3c, 0x00,
|
||||
0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x66, 0x66, 0x66,
|
||||
0x66, 0x66, 0x3c, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x24, 0x18, 0x00,
|
||||
0x00, 0xc6, 0xc6, 0xc6, 0xd6, 0xee, 0x44, 0x00, 0x00, 0x66, 0x66, 0x3c,
|
||||
0x66, 0x66, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x18, 0x00,
|
||||
0x00, 0x7e, 0x0c, 0x18, 0x30, 0x60, 0x7e, 0x00, 0x1e, 0x18, 0x18, 0x18,
|
||||
0x18, 0x18, 0x1e, 0x00, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x03, 0x00,
|
||||
0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78, 0x00, 0x00, 0x18, 0x3c, 0x66,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
|
||||
0x00, 0x18, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x06,
|
||||
0x3e, 0x66, 0x3e, 0x00, 0x00, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x7c, 0x00,
|
||||
0x00, 0x00, 0x3c, 0x66, 0x60, 0x66, 0x3c, 0x00, 0x00, 0x06, 0x06, 0x3e,
|
||||
0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x7e, 0x60, 0x3c, 0x00,
|
||||
0x00, 0x1c, 0x36, 0x30, 0x78, 0x30, 0x30, 0x00, 0x00, 0x00, 0x3e, 0x66,
|
||||
0x66, 0x3e, 0x06, 0x3c, 0x00, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x00,
|
||||
0x00, 0x18, 0x00, 0x38, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x0c, 0x00, 0x0c,
|
||||
0x0c, 0x0c, 0x0c, 0x38, 0x00, 0x60, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x00,
|
||||
0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1c, 0x00, 0x00, 0x00, 0xfc, 0xd6,
|
||||
0xd6, 0xd6, 0xd6, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x00,
|
||||
0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x7c, 0x66,
|
||||
0x66, 0x7c, 0x60, 0x60, 0x00, 0x00, 0x3e, 0x66, 0x66, 0x3e, 0x06, 0x06,
|
||||
0x00, 0x00, 0x3c, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x3c, 0x60,
|
||||
0x3c, 0x06, 0x7c, 0x00, 0x00, 0x30, 0x78, 0x30, 0x30, 0x30, 0x1c, 0x00,
|
||||
0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x66, 0x66,
|
||||
0x66, 0x24, 0x18, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xd6, 0xee, 0x44, 0x00,
|
||||
0x00, 0x00, 0x66, 0x66, 0x3c, 0x66, 0x66, 0x00, 0x00, 0x00, 0x66, 0x66,
|
||||
0x66, 0x3e, 0x06, 0x3c, 0x00, 0x00, 0x7e, 0x0c, 0x18, 0x30, 0x7e, 0x00,
|
||||
0x1c, 0x30, 0x30, 0xe0, 0x30, 0x30, 0x1c, 0x00, 0x18, 0x18, 0x18, 0x18,
|
||||
0x18, 0x18, 0x18, 0x00, 0x70, 0x18, 0x18, 0x0e, 0x18, 0x18, 0x70, 0x00,
|
||||
0x00, 0x34, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x42, 0x99, 0xa1,
|
||||
0xa1, 0x99, 0x42, 0x3c, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x00,
|
||||
0x00, 0x7c, 0x60, 0x7c, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x7c, 0x66, 0x7c,
|
||||
0x66, 0x66, 0x7c, 0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00,
|
||||
0x00, 0x1e, 0x36, 0x66, 0x66, 0x66, 0xff, 0xc3, 0x00, 0x7e, 0x60, 0x78,
|
||||
0x60, 0x60, 0x7e, 0x00, 0x00, 0xd6, 0xd6, 0x7c, 0x7c, 0xd6, 0xd6, 0x00,
|
||||
0x00, 0x3c, 0x66, 0x0c, 0x06, 0x66, 0x3c, 0x00, 0x00, 0x62, 0x66, 0x6e,
|
||||
0x76, 0x66, 0x46, 0x00, 0x04, 0x6a, 0x66, 0x6e, 0x76, 0x66, 0x46, 0x00,
|
||||
0x00, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x66, 0x00, 0x00, 0x1e, 0x36, 0x66,
|
||||
0x66, 0x66, 0x66, 0x00, 0x00, 0x82, 0xc6, 0xee, 0xd6, 0xc6, 0xc6, 0x00,
|
||||
0x00, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66,
|
||||
0x66, 0x66, 0x3c, 0x00, 0x00, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00,
|
||||
0x00, 0x7c, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x00, 0x00, 0x3c, 0x66, 0x60,
|
||||
0x60, 0x66, 0x3c, 0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00,
|
||||
0x00, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x3c, 0x00, 0x00, 0x7c, 0xd6, 0xd6,
|
||||
0xd6, 0x7c, 0x10, 0x00, 0x00, 0x66, 0x66, 0x3c, 0x66, 0x66, 0x66, 0x00,
|
||||
0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7f, 0x01, 0x00, 0x66, 0x66, 0x66,
|
||||
0x3e, 0x06, 0x06, 0x00, 0x00, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xfe, 0x00,
|
||||
0x00, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xff, 0x01, 0x00, 0xe0, 0x60, 0x7c,
|
||||
0x66, 0x66, 0x7c, 0x00, 0x00, 0xc6, 0xc6, 0xf6, 0xde, 0xde, 0xf6, 0x00,
|
||||
0x00, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x3c, 0x66, 0x0e,
|
||||
0x06, 0x66, 0x3c, 0x00, 0x00, 0xce, 0xdb, 0xfb, 0xdb, 0xdb, 0xce, 0x00,
|
||||
0x00, 0x3e, 0x66, 0x66, 0x3e, 0x66, 0x66, 0x00, 0x00, 0x00, 0x3c, 0x06,
|
||||
0x3e, 0x66, 0x3e, 0x00, 0x00, 0x3c, 0x60, 0x3c, 0x66, 0x66, 0x3c, 0x00,
|
||||
0x00, 0x00, 0x7c, 0x66, 0x7c, 0x66, 0x7c, 0x00, 0x00, 0x00, 0x3c, 0x06,
|
||||
0x3c, 0x60, 0x3e, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x3c, 0x00,
|
||||
0x00, 0x00, 0x3c, 0x66, 0x7e, 0x60, 0x3c, 0x00, 0x00, 0x00, 0xd6, 0x7c,
|
||||
0x7c, 0xd6, 0xd6, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x0c, 0x66, 0x3c, 0x00,
|
||||
0x00, 0x00, 0x62, 0x66, 0x6e, 0x76, 0x66, 0x00, 0x00, 0x04, 0x6a, 0x66,
|
||||
0x6e, 0x76, 0x66, 0x00, 0x00, 0x00, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x00,
|
||||
0x00, 0x00, 0x1e, 0x36, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x82, 0xc6,
|
||||
0xee, 0xd6, 0xc6, 0x00, 0x00, 0x00, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x00,
|
||||
0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x7e, 0x66,
|
||||
0x66, 0x66, 0x66, 0x00, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88,
|
||||
0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x77, 0xdd, 0x77, 0xdd,
|
||||
0x77, 0xdd, 0x77, 0xdd, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
|
||||
0x18, 0x18, 0x18, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x18,
|
||||
0xf8, 0x18, 0x18, 0x18, 0x66, 0x66, 0x66, 0xe6, 0x66, 0x66, 0x66, 0x66,
|
||||
0x00, 0x00, 0x00, 0xfe, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0xf8, 0x18,
|
||||
0xf8, 0x18, 0x18, 0x18, 0x66, 0x66, 0xe6, 0x06, 0xe6, 0x66, 0x66, 0x66,
|
||||
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0xfe, 0x06,
|
||||
0xe6, 0x66, 0x66, 0x66, 0x66, 0x66, 0xe6, 0x06, 0xfe, 0x00, 0x00, 0x00,
|
||||
0x66, 0x66, 0x66, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0xf8, 0x18,
|
||||
0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x18, 0x18, 0x18, 0x18,
|
||||
0x18, 0x18, 0x18, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x18, 0x18, 0x18, 0x18,
|
||||
0x18, 0x18, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0xff, 0x18, 0x18, 0x18, 0x18,
|
||||
0x18, 0x18, 0x1f, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x66, 0x66, 0x66, 0x67,
|
||||
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x67, 0x60, 0x7f, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x7f, 0x60, 0x67, 0x66, 0x66, 0x66, 0x66, 0x66, 0xe7, 0x00,
|
||||
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xe7, 0x66, 0x66, 0x66,
|
||||
0x66, 0x66, 0x67, 0x60, 0x67, 0x66, 0x66, 0x66, 0x00, 0x00, 0xff, 0x00,
|
||||
0xff, 0x00, 0x00, 0x00, 0x66, 0x66, 0xe7, 0x00, 0xe7, 0x66, 0x66, 0x66,
|
||||
0x18, 0x18, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x18, 0x18, 0x18,
|
||||
0x00, 0x00, 0x00, 0xff, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7f,
|
||||
0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x1f, 0x18, 0x1f, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x1f, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x7f,
|
||||
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0xff, 0x66, 0x66, 0x66, 0x66,
|
||||
0x18, 0x18, 0xff, 0x00, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x18, 0x18, 0x18, 0x18,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
|
||||
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x7c, 0x60, 0x60,
|
||||
0x00, 0x00, 0x3c, 0x66, 0x60, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x7e, 0x18,
|
||||
0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x3c,
|
||||
0x00, 0x00, 0x7c, 0xd6, 0xd6, 0x7c, 0x10, 0x10, 0x00, 0x00, 0x66, 0x66,
|
||||
0x3c, 0x66, 0x66, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x7f, 0x01,
|
||||
0x00, 0x00, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x00, 0x00, 0x00, 0xd6, 0xd6,
|
||||
0xd6, 0xd6, 0xfe, 0x00, 0x00, 0x00, 0xd6, 0xd6, 0xd6, 0xd6, 0xff, 0x01,
|
||||
0x00, 0x00, 0xe0, 0x60, 0x7c, 0x66, 0x7c, 0x00, 0x00, 0x00, 0xc6, 0xc6,
|
||||
0xf6, 0xde, 0xf6, 0x00, 0x00, 0x00, 0x60, 0x60, 0x7c, 0x66, 0x7c, 0x00,
|
||||
0x00, 0x00, 0x3c, 0x66, 0x0e, 0x66, 0x3c, 0x00, 0x00, 0x00, 0xce, 0xdb,
|
||||
0xfb, 0xdb, 0xce, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x3e, 0x66, 0x66, 0x00,
|
||||
0x24, 0x7e, 0x60, 0x78, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x24, 0x3c, 0x66,
|
||||
0x7e, 0x60, 0x3c, 0x00, 0x30, 0x18, 0x0c, 0x18, 0x30, 0x00, 0x7c, 0x00,
|
||||
0x18, 0x30, 0x60, 0x30, 0x18, 0x00, 0x7c, 0x00, 0x00, 0x0e, 0x1b, 0x18,
|
||||
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xd8, 0x70, 0x00,
|
||||
0x00, 0x18, 0x00, 0x7e, 0x00, 0x18, 0x00, 0x00, 0x00, 0x76, 0xdc, 0x00,
|
||||
0x76, 0xdc, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x18, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x18, 0x00, 0x00, 0x00, 0x0e, 0x0e, 0x0c, 0x0c, 0x6c, 0x3c, 0x1c, 0x00,
|
||||
0x8f, 0xcd, 0xef, 0xfc, 0xdc, 0xcc, 0xcc, 0x00, 0x38, 0x6c, 0x18, 0x30,
|
||||
0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x3c, 0x3c, 0x3c, 0x00, 0x00,
|
||||
0xaa, 0x54, 0xaa, 0x54, 0xaa, 0x54, 0xaa, 0x00
|
||||
};
|
||||
+8408
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,584 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "main.h"
|
||||
|
||||
// Types
|
||||
typedef struct
|
||||
{
|
||||
u8 f_mul; // PLL multiplier
|
||||
u8 f_div; // Pixel Clock divisor
|
||||
u16 h_fporch; // Horizontal front porch size
|
||||
u16 h_sync; // Horizontal sync size
|
||||
u16 h_bporch; // Horizontal back porch size
|
||||
u16 h_visible; // Horizontal visible area size
|
||||
u16 v_fporch; // Vertical front porch size
|
||||
u16 v_sync; // Vertical sync size
|
||||
u16 v_bporch; // Vertical back porch size
|
||||
u16 v_visible; // Vertical visible area size
|
||||
} FT_MODE;
|
||||
|
||||
/*
|
||||
| # | visible | Fpll MHz | Fpix MHz | clocks/line | lines/frame | line kHz | frame Hz |
|
||||
| -- | -------- | -------- | -------- | ----------- | ----------- | -------- | -------- |
|
||||
| 0 | 640x480 | 48 | 24 | 800 | 524 | 30.000 | 57.252 |
|
||||
| 1 | 640x480 | 64 | 32 | 832 | 520 | 38.462 | 73.964 |
|
||||
| 2 | 640x480 | 64 | 32 | 800 | 524 | 40.000 | 76.336 |
|
||||
| 3 | 800x600 | 40 | 40 | 1056 | 628 | 37.879 | 60.317 |
|
||||
| 4 | 800x600 | 80 | 40 | 1056 | 628 | 37.879 | 60.317 |
|
||||
| 5 | 800x600 | 48 | 48 | 1040 | 666 | 46.154 | 69.300 |
|
||||
| 6 | 800x600 | 56 | 56 | 1048 | 631 | 53.435 | 84.683 |
|
||||
| 7 | 1024x768 | 64 | 64 | 1344 | 806 | 47.619 | 59.081 |
|
||||
| 8 | 1024x768 | 72 | 72 | 1328 | 806 | 54.217 | 67.267 |
|
||||
| 9 | 1024x768 | 80 | 80 | 1312 | 800 | 60.976 | 76.220 |
|
||||
| 10 | 640x1024 | 56 | 56 | 844 | 1066 | 66.351 | 62.243 |
|
||||
| 11 | 1280x720 | 72 | 72 | 1650 | 750 | 43.636 | 58.182 |
|
||||
| 12 | 1280x720 | 72 | 72 | 1600 | 750 | 45.000 | 60.000 |
|
||||
*/
|
||||
|
||||
enum // const FT_MODE ft_modes[] in ft8xx.c
|
||||
{
|
||||
FT_MODE_640_480_57 = 0, // 0: 640x480@57Hz (48MHz)
|
||||
FT_MODE_640_480_74 = 1, // 1: 640x480@74Hz (64MHz)
|
||||
FT_MODE_640_480_76 = 2, // 2: 640x480@76Hz (64MHz)
|
||||
FT_MODE_800_600_60 = 3, // 3: 800x600@60Hz (40MHz)
|
||||
FT_MODE_800_600_60_80MHZ = 4, // 4: 800x600@60Hz (80MHz)
|
||||
FT_MODE_800_600_69 = 5, // 5: 800x600@69Hz (48MHz)
|
||||
FT_MODE_800_600_85 = 6, // 6: 800x600@85Hz (56MHz)
|
||||
FT_MODE_1024_768_59 = 7, // 7: 1024x768@59Hz (64MHz)
|
||||
FT_MODE_1024_768_67 = 8, // 8: 1024x768@67Hz (72MHz)
|
||||
FT_MODE_1024_768_76 = 9, // 9: 1024x768@76Hz (80MHz)
|
||||
FT_MODE_1280_1024_60_HALF = 10, // 10: 1280/2x1024@60Hz (56MHz)
|
||||
FT_MODE_1280_720_58 = 11, // 11: 1280x720@58Hz (72MHz)
|
||||
FT_MODE_1280_720_60 = 12, // 12: 1280x720@60Hz (72MHz)
|
||||
FT_MODE_MAX
|
||||
};
|
||||
|
||||
// Memory addresses
|
||||
#define FT_RAM_G 0x000000 // Main graphics RAM
|
||||
#define FT_ROM_CHIPID 0x0C0000 // Chip ID and revision
|
||||
#define FT_ROM_FONT 0x1E0000 // Fonts
|
||||
#define FT_ROM_FONT_ADDR 0x2FFFFC // Font table pointer address
|
||||
#define FT_RAM_DL 0x300000 // Display list RAM
|
||||
#define FT_RAM_REG 0x302000 // Registers
|
||||
#define FT_RAM_CMD 0x308000 // Coprocessor command buffer
|
||||
#define FT_RAM_ERR_REPORT 0x309800 // Error message
|
||||
|
||||
// Commands
|
||||
#define FT_CMD_ACTIVE 0x00 // cc 00 00
|
||||
#define FT_CMD_STANDBY 0x41 // cc 00 00
|
||||
#define FT_CMD_SLEEP 0x42 // cc 00 00
|
||||
#define FT_CMD_PWRDOWN 0x43 // cc 00 00
|
||||
#define FT_CMD_CLKEXT 0x44 // cc 00 00
|
||||
#define FT_CMD_CLKINT 0x48 // cc 00 00
|
||||
#define FT_CMD_PDROMS 0x49 // cc xx 00
|
||||
#define FT_CMD_CLKSEL 0x61 // cc xx 00 -> [5:0] - mul, [7:6] - PLL range (0 for mul=0..3, 1 for mul=4..5)
|
||||
#define FT_CMD_RST_PULSE 0x68 // cc 00 00
|
||||
|
||||
// ID
|
||||
#define FT_ID 0x7C
|
||||
|
||||
// Registers
|
||||
#define FT_REG_ID 0x302000
|
||||
#define FT_REG_FRAMES 0x302004
|
||||
#define FT_REG_CLOCK 0x302008
|
||||
#define FT_REG_FREQUENCY 0x30200C
|
||||
#define FT_REG_RENDERMODE 0x302010
|
||||
#define FT_REG_SNAPY 0x302014
|
||||
#define FT_REG_SNAPSHOT 0x302018
|
||||
#define FT_REG_SNAPFORMAT 0x30201C
|
||||
#define FT_REG_CPURESET 0x302020
|
||||
#define FT_REG_TAP_CRC 0x302024
|
||||
#define FT_REG_TAP_MASK 0x302028
|
||||
#define FT_REG_HCYCLE 0x30202C
|
||||
#define FT_REG_HOFFSET 0x302030
|
||||
#define FT_REG_HSIZE 0x302034
|
||||
#define FT_REG_HSYNC0 0x302038
|
||||
#define FT_REG_HSYNC1 0x30203C
|
||||
#define FT_REG_VCYCLE 0x302040
|
||||
#define FT_REG_VOFFSET 0x302044
|
||||
#define FT_REG_VSIZE 0x302048
|
||||
#define FT_REG_VSYNC0 0x30204C
|
||||
#define FT_REG_VSYNC1 0x302050
|
||||
#define FT_REG_DLSWAP 0x302054
|
||||
#define FT_REG_ROTATE 0x302058
|
||||
#define FT_REG_OUTBITS 0x30205C
|
||||
#define FT_REG_DITHER 0x302060
|
||||
#define FT_REG_SWIZZLE 0x302064
|
||||
#define FT_REG_CSPREAD 0x302068
|
||||
#define FT_REG_PCLK_POL 0x30206C
|
||||
#define FT_REG_PCLK 0x302070
|
||||
#define FT_REG_TAG_X 0x302074
|
||||
#define FT_REG_TAG_Y 0x302078
|
||||
#define FT_REG_TAG 0x30207C
|
||||
#define FT_REG_VOL_PB 0x302080
|
||||
#define FT_REG_VOL_SOUND 0x302084
|
||||
#define FT_REG_SOUND 0x302088
|
||||
#define FT_REG_PLAY 0x30208C
|
||||
#define FT_REG_GPIO_DIR 0x302090
|
||||
#define FT_REG_GPIO 0x302094
|
||||
#define FT_REG_GPIOX_DIR 0x302098
|
||||
#define FT_REG_GPIOX 0x30209C
|
||||
#define FT_REG_INT_FLAGS 0x3020A8
|
||||
#define FT_REG_INT_EN 0x3020AC
|
||||
#define FT_REG_INT_MASK 0x3020B0
|
||||
#define FT_REG_PLAYBACK_START 0x3020B4
|
||||
#define FT_REG_PLAYBACK_LENGTH 0x3020B8
|
||||
#define FT_REG_PLAYBACK_READPTR 0x3020BC
|
||||
#define FT_REG_PLAYBACK_FREQ 0x3020C0
|
||||
#define FT_REG_PLAYBACK_FORMAT 0x3020C4
|
||||
#define FT_REG_PLAYBACK_LOOP 0x3020C8
|
||||
#define FT_REG_PLAYBACK_PLAY 0x3020CC
|
||||
#define FT_REG_PWM_HZ 0x3020D0
|
||||
#define FT_REG_PWM_DUTY 0x3020D4
|
||||
#define FT_REG_MACRO_0 0x3020D8
|
||||
#define FT_REG_MACRO_1 0x3020DC
|
||||
#define FT_REG_CMD_READ 0x3020F8
|
||||
#define FT_REG_CMD_WRITE 0x3020FC
|
||||
#define FT_REG_CMD_DL 0x302100
|
||||
#define FT_REG_TOUCH_MODE 0x302104
|
||||
#define FT_REG_TOUCH_ADC_MODE 0x302108
|
||||
#define FT_REG_TOUCH_CHARGE 0x30210C
|
||||
#define FT_REG_TOUCH_SETTLE 0x302110
|
||||
#define FT_REG_TOUCH_OVERSAMPLE 0x302114
|
||||
#define FT_REG_TOUCH_RZTHRESH 0x302118
|
||||
#define FT_REG_TOUCH_RAW_XY 0x30211C
|
||||
#define FT_REG_TOUCH_RZ 0x302120
|
||||
#define FT_REG_TOUCH_SCREEN_XY 0x302124
|
||||
#define FT_REG_TOUCH_TAG_XY 0x302128
|
||||
#define FT_REG_TOUCH_TAG 0x30212C
|
||||
#define FT_REG_TOUCH_TAG1_XY 0x302130
|
||||
#define FT_REG_TOUCH_TAG1 0x302134
|
||||
#define FT_REG_TOUCH_TAG2_XY 0x302138
|
||||
#define FT_REG_TOUCH_TAG2 0x30213C
|
||||
#define FT_REG_TOUCH_TAG3_XY 0x302140
|
||||
#define FT_REG_TOUCH_TAG3 0x302144
|
||||
#define FT_REG_TOUCH_TAG4_XY 0x302148
|
||||
#define FT_REG_TOUCH_TAG4 0x30214C
|
||||
#define FT_REG_TOUCH_TRANSFORM_A 0x302150
|
||||
#define FT_REG_TOUCH_TRANSFORM_B 0x302154
|
||||
#define FT_REG_TOUCH_TRANSFORM_C 0x302158
|
||||
#define FT_REG_TOUCH_TRANSFORM_D 0x30215C
|
||||
#define FT_REG_TOUCH_TRANSFORM_E 0x302160
|
||||
#define FT_REG_TOUCH_TRANSFORM_F 0x302164
|
||||
#define FT_REG_TOUCH_CONFIG 0x302168
|
||||
#define FT_REG_CTOUCH_TOUCH4_X 0x30216C
|
||||
#define FT_REG_BIST_EN 0x302174
|
||||
#define FT_REG_TRIM 0x302180
|
||||
#define FT_REG_ANA_COMP 0x302184
|
||||
#define FT_REG_SPI_WIDTH 0x302188
|
||||
#define FT_SPI_WIDTH_SINGLE 0
|
||||
#define FT_SPI_WIDTH_DUAL 1
|
||||
#define FT_SPI_WIDTH_QUAD 2
|
||||
#define FT_REG_TOUCH_DIRECT_XY 0x30218C
|
||||
#define FT_REG_TOUCH_DIRECT_Z1Z2 0x302190
|
||||
#define FT_REG_DATESTAMP 0x302564
|
||||
#define FT_REG_CMDB_SPACE 0x302574
|
||||
#define FT_REG_CMDB_WRITE 0x302578
|
||||
#define FT_REG_ADAPTIVE_FRAMERATE 0x30257C
|
||||
#define FT_REG_PLAYBACK_PAUSE 0x3025EC
|
||||
#define FT_REG_FLASH_STATUS 0x3025F0
|
||||
#define REG_MEDIAFIFO_READ 0x309014
|
||||
#define REG_MEDIAFIFO_WRITE 0x309018
|
||||
#define FT_REG_FLASH_SIZE 0x309024
|
||||
|
||||
#define FT_REG_TRACKER 0x309000
|
||||
#define FT_REG_TRACKER_1 0x309004
|
||||
#define FT_REG_TRACKER_2 0x309008
|
||||
#define FT_REG_TRACKER_3 0x30900C
|
||||
#define FT_REG_TRACKER_4 0x309010
|
||||
#define FT_REG_MEDIAFIFO_READ 0x309014
|
||||
#define FT_REG_MEDIAFIFO_WRITE 0x309018
|
||||
|
||||
// Co-processor commands
|
||||
#define FT_CCMD_APPEND 0xFFFFFF1E
|
||||
#define FT_CCMD_BGCOLOR 0xFFFFFF09
|
||||
#define FT_CCMD_BITMAP_TRANSFORM 0xFFFFFF21
|
||||
#define FT_CCMD_BUTTON 0xFFFFFF0D
|
||||
#define FT_CCMD_CALIBRATE 0xFFFFFF15
|
||||
#define FT_CCMD_CLOCK 0xFFFFFF14
|
||||
#define FT_CCMD_COLDSTART 0xFFFFFF32
|
||||
#define FT_CCMD_CRC 0xFFFFFF03
|
||||
#define FT_CCMD_CSKETCH 0xFFFFFF35
|
||||
#define FT_CCMD_DIAL 0xFFFFFF2D
|
||||
#define FT_CCMD_DLSTART 0xFFFFFF00
|
||||
#define FT_CCMD_EXECUTE 0xFFFFFF07
|
||||
#define FT_CCMD_FGCOLOR 0xFFFFFF0A
|
||||
#define FT_CCMD_FLASHATTACH 0xFFFFFF49
|
||||
#define FT_CCMD_FLASHDETACH 0xFFFFFF48
|
||||
#define FT_CCMD_FLASHERASE 0xFFFFFF44
|
||||
#define FT_CCMD_FLASHFAST 0xFFFFFF4A
|
||||
#define FT_CCMD_FLASHRX 0xFFFFFF4D
|
||||
#define FT_CCMD_FLASHSOURCE 0xFFFFFF4E
|
||||
#define FT_CCMD_FLASHSPIDESEL 0xFFFFFF4B
|
||||
#define FT_CCMD_FLASHTX 0xFFFFFF4C
|
||||
#define FT_CCMD_FLASHUPDATE 0xFFFFFF47
|
||||
#define FT_CCMD_GAUGE 0xFFFFFF13
|
||||
#define FT_CCMD_GETMATRIX 0xFFFFFF33
|
||||
#define FT_CCMD_GETPOINT 0xFFFFFF08
|
||||
#define FT_CCMD_GETPROPS 0xFFFFFF25
|
||||
#define FT_CCMD_GETPTR 0xFFFFFF23
|
||||
#define FT_CCMD_GRADCOLOR 0xFFFFFF34
|
||||
#define FT_CCMD_GRADIENT 0xFFFFFF0B
|
||||
#define FT_CCMD_HAMMERAUX 0xFFFFFF04
|
||||
#define FT_CCMD_IDCT_DELETED 0xFFFFFF06
|
||||
#define FT_CCMD_INFLATE 0xFFFFFF22
|
||||
#define FT_CCMD_INTERRUPT 0xFFFFFF02
|
||||
#define FT_CCMD_INT_RAMSHARED 0xFFFFFF3D
|
||||
#define FT_CCMD_INT_SWLOADIMAGE 0xFFFFFF3E
|
||||
#define FT_CCMD_KEYS 0xFFFFFF0E
|
||||
#define FT_CCMD_LOADIDENTITY 0xFFFFFF26
|
||||
#define FT_CCMD_LOADIMAGE 0xFFFFFF24
|
||||
#define FT_CCMD_LOGO 0xFFFFFF31
|
||||
#define FT_CCMD_MARCH 0xFFFFFF05
|
||||
#define FT_CCMD_MEDIAFIFO 0xFFFFFF39
|
||||
#define FT_CCMD_MEMCPY 0xFFFFFF1D
|
||||
#define FT_CCMD_MEMCRC 0xFFFFFF18
|
||||
#define FT_CCMD_MEMSET 0xFFFFFF1B
|
||||
#define FT_CCMD_MEMWRITE 0xFFFFFF1A
|
||||
#define FT_CCMD_MEMZERO 0xFFFFFF1C
|
||||
#define FT_CCMD_NUMBER 0xFFFFFF2E
|
||||
#define FT_CCMD_PLAYVIDEO 0xFFFFFF3A
|
||||
#define FT_CCMD_PROGRESS 0xFFFFFF0F
|
||||
#define FT_CCMD_REGREAD 0xFFFFFF19
|
||||
#define FT_CCMD_ROMFONT 0xFFFFFF3F
|
||||
#define FT_CCMD_ROTATE 0xFFFFFF29
|
||||
#define FT_CCMD_SCALE 0xFFFFFF28
|
||||
#define FT_CCMD_SCREENSAVER 0xFFFFFF2F
|
||||
#define FT_CCMD_SCROLLBAR 0xFFFFFF11
|
||||
#define FT_CCMD_SETBASE 0xFFFFFF38
|
||||
#define FT_CCMD_SETBITMAP 0xFFFFFF43
|
||||
#define FT_CCMD_SETFONT 0xFFFFFF2B
|
||||
#define FT_CCMD_SETFONT2 0xFFFFFF3B
|
||||
#define FT_CCMD_SETMATRIX 0xFFFFFF2A
|
||||
#define FT_CCMD_SETROTATE 0xFFFFFF36
|
||||
#define FT_CCMD_SETSCRATCH 0xFFFFFF3C
|
||||
#define FT_CCMD_SKETCH 0xFFFFFF30
|
||||
#define FT_CCMD_SLIDER 0xFFFFFF10
|
||||
#define FT_CCMD_SNAPSHOT 0xFFFFFF1F
|
||||
#define FT_CCMD_SNAPSHOT2 0xFFFFFF37
|
||||
#define FT_CCMD_SPINNER 0xFFFFFF16
|
||||
#define FT_CCMD_STOP 0xFFFFFF17
|
||||
#define FT_CCMD_SWAP 0xFFFFFF01
|
||||
#define FT_CCMD_SYNC 0xFFFFFF42
|
||||
#define FT_CCMD_TEXT 0xFFFFFF0C
|
||||
#define FT_CCMD_TOGGLE 0xFFFFFF12
|
||||
#define FT_CCMD_TOUCH_TRANSFORM 0xFFFFFF20
|
||||
#define FT_CCMD_TRACK 0xFFFFFF2C
|
||||
#define FT_CCMD_TRANSLATE 0xFFFFFF27
|
||||
#define FT_CCMD_VIDEOFRAME 0xFFFFFF41
|
||||
#define FT_CCMD_VIDEOSTART 0xFFFFFF40
|
||||
|
||||
#define FT_OPT_CENTER 1536UL
|
||||
#define FT_OPT_CENTERX 512UL
|
||||
#define FT_OPT_CENTERY 1024UL
|
||||
#define FT_OPT_FLAT 256UL
|
||||
#define FT_OPT_MONO 1UL
|
||||
#define FT_OPT_NOBACK 4096UL
|
||||
#define FT_OPT_NODL 2UL
|
||||
#define FT_OPT_NOHANDS 49152UL
|
||||
#define FT_OPT_NOHM 16384UL
|
||||
#define FT_OPT_NOPOINTER 16384UL
|
||||
#define FT_OPT_NOSECS 32768UL
|
||||
#define FT_OPT_NOTICKS 8192UL
|
||||
#define FT_OPT_RIGHTX 2048UL
|
||||
#define FT_OPT_SIGNED 256UL
|
||||
#define OPT_NOTEAR 4UL
|
||||
#define OPT_FULLSCREEN 8UL
|
||||
#define OPT_MEDIAFIFO 16UL
|
||||
#define OPT_SOUND 32UL
|
||||
|
||||
// Primitives
|
||||
#define FT_BITMAPS 1
|
||||
#define FT_POINTS 2
|
||||
#define FT_LINES 3
|
||||
#define FT_LINE_STRIP 4
|
||||
#define FT_EDGE_STRIP_R 5
|
||||
#define FT_EDGE_STRIP_L 6
|
||||
#define FT_EDGE_STRIP_A 7
|
||||
#define FT_EDGE_STRIP_B 8
|
||||
#define FT_RECTS 9
|
||||
|
||||
// Formats
|
||||
#define FT_ARGB1555 0
|
||||
#define FT_L1 1
|
||||
#define FT_L4 2
|
||||
#define FT_L8 3
|
||||
#define FT_RGB332 4
|
||||
#define FT_ARGB2 5
|
||||
#define FT_ARGB4 6
|
||||
#define FT_RGB565 7
|
||||
#define FT_TEXT8X8 9
|
||||
#define FT_TEXTVGA 10
|
||||
#define FT_BARGRAPH 11
|
||||
#define FT_PALETTED565 14
|
||||
#define FT_PALETTED4444 15
|
||||
#define FT_PALETTED8 16
|
||||
#define FT_L2 17
|
||||
|
||||
// Blend functions
|
||||
#define FT_ZERO 0
|
||||
#define FT_ONE 1
|
||||
#define FT_SRC_ALPHA 2
|
||||
#define FT_DST_ALPHA 3
|
||||
#define FT_ONE_MINUS_SRC_ALPHA 4
|
||||
#define FT_ONE_MINUS_DST_ALPHA 5
|
||||
|
||||
// Stencil test functions
|
||||
#define FT_NEVER 0
|
||||
#define FT_LESS 1
|
||||
#define FT_LEQUAL 2
|
||||
#define FT_GREATER 3
|
||||
#define FT_GEQUAL 4
|
||||
#define FT_EQUAL 5
|
||||
#define FT_NOTEQUAL 6
|
||||
#define FT_ALWAYS 7
|
||||
|
||||
// Stencil operations
|
||||
#define FT_KEEP 1
|
||||
#define FT_REPLACE 2
|
||||
#define FT_INCR 3
|
||||
#define FT_DECR 4
|
||||
#define FT_INVERT 5
|
||||
#define FT_INCR_WRAP 6 // undocumented???
|
||||
#define FT_DECR_WRAP 7 // undocumented???
|
||||
|
||||
// Bitmap wrap mode
|
||||
#define FT_REPEAT 1
|
||||
#define FT_BORDER 0
|
||||
|
||||
// Bitmap filtering mode
|
||||
#define FT_NEAREST 0
|
||||
#define FT_BILINEAR 1
|
||||
|
||||
// DL_SWAP modes
|
||||
#define FT_DLSWAP_DONE 0
|
||||
#define FT_DLSWAP_LINE 1
|
||||
#define FT_DLSWAP_FRAME 2
|
||||
|
||||
// INT sources
|
||||
#define FT_INT_SWAP 1
|
||||
#define FT_INT_TOUCH 2
|
||||
#define FT_INT_TAG 4
|
||||
#define FT_INT_SOUND 8
|
||||
#define FT_INT_PLAYBACK 16
|
||||
#define FT_INT_CMDEMPTY 32
|
||||
#define FT_INT_CMDFLAG 64
|
||||
#define FT_INT_CONVCOMPLETE 128
|
||||
|
||||
// Playback formats
|
||||
#define FT_LINEAR_SAMPLES 0
|
||||
#define FT_ULAW_SAMPLES 1
|
||||
#define FT_ADPCM_SAMPLES 2
|
||||
|
||||
// Touch ADC modes
|
||||
#define FT_ADC_SINGLE_ENDED 0
|
||||
#define FT_ADC_DIFFERENTIAL 1
|
||||
|
||||
// Touch modes
|
||||
#define FT_TOUCHMODE_OFF 0
|
||||
#define FT_TOUCHMODE_ONESHOT 1
|
||||
#define FT_TOUCHMODE_FRAME 2
|
||||
#define FT_TOUCHMODE_CONTINUOUS 3
|
||||
|
||||
// Constants
|
||||
#define FT_DL_SIZE 8192 // 8KB Display List buffer size
|
||||
#define FT_CMD_FIFO_SIZE 4096 // 4KB coprocessor Fifo size
|
||||
#define FT_CMD_SIZE 4 // 4 byte per coprocessor command of EVE
|
||||
#define FT_GPU_NUMCHAR_PERFONT 128
|
||||
#define FT_GPU_FONT_TABLE_SIZE 148
|
||||
|
||||
// Co-processor
|
||||
void ft_Append(u32 ptr, u32 num);
|
||||
void ft_BgColor(u32 c);
|
||||
void ft_BitmapTransform(i32 x0, i32 y0, i32 x1, i32 y1, i32 x2, i32 y2, i32 tx0, i32 ty0, i32 tx1, i32 ty1, i32 tx2, i32 ty2, u16 result);
|
||||
void ft_Button(i16 x, i16 y, i16 w, i16 h, i16 font, u16 options, const char *s);
|
||||
void ft_Calibrate(u32 result);
|
||||
void ft_Clock(i16 x, i16 y, i16 r, u16 options, u16 h, u16 m, u16 s, u16 ms);
|
||||
void ft_ColdStart();
|
||||
void ft_Dial(i16 x, i16 y, i16 r, u16 options, u16 val);
|
||||
void ft_Dlstart();
|
||||
void ft_FgColor(u32 c);
|
||||
void ft_FlashAttach();
|
||||
void ft_FlashDetach();
|
||||
void ft_FlashErase();
|
||||
void ft_FlashFast(u32);
|
||||
void ft_FlashRx(u32, u32);
|
||||
void ft_FlashSpiDesel();
|
||||
void ft_FlashTx(u32);
|
||||
void ft_FlashUpdate(u32, u32, u32);
|
||||
void ft_Gauge(i16 x, i16 y, i16 r, u16 options, u16 major, u16 minor, u16 val, u16 range);
|
||||
void ft_GetMatrix(i32 a, i32 b, i32 c, i32 d, i32 e, i32 f);
|
||||
void ft_GetProps(u32 ptr, u32 w, u32 h);
|
||||
void ft_GetPtr(u32 result);
|
||||
void ft_GradColor(u32 c);
|
||||
void ft_Gradient(i16 x0, i16 y0, u32 rgb0, i16 x1, i16 y1, u32 rgb1);
|
||||
void ft_Inflate(u32 ptr);
|
||||
void ft_Int_RAMShared(u32 ptr);
|
||||
void ft_Int_SWLoadImage(u32 ptr, u32 options);
|
||||
void ft_Interrupt(u32 ms);
|
||||
void ft_Keys(i16 x, i16 y, i16 w, i16 h, i16 font, u16 options, const char *s);
|
||||
void ft_LoadIdentity();
|
||||
void ft_LoadImage(u32 ptr, u32 options);
|
||||
void ft_Logo();
|
||||
void ft_MediaFifo(u32 ptr, u32 size);
|
||||
void ft_MemCrc(u32 ptr, u32 num, u32 result);
|
||||
void ft_MemSet(u32 ptr, u32 value, u32 num);
|
||||
void ft_MemWrite(u32 ptr, u32 num);
|
||||
void ft_MemZero(u32 ptr, u32 num);
|
||||
void ft_Memcpy(u32 dest, u32 src, u32 num);
|
||||
void ft_Number(i16 x, i16 y, i16 font, u16 options, i32 n);
|
||||
void ft_PlayVideo(u32 options);
|
||||
void ft_Progress(i16 x, i16 y, i16 w, i16 h, u16 options, u16 val, u16 range);
|
||||
void ft_RegRead(u32 ptr, u32 result);
|
||||
void ft_RomFont(u32 font, u32 romslot);
|
||||
void ft_Rotate(i32 a);
|
||||
void ft_Scale(i32 sx, i32 sy);
|
||||
void ft_ScreenSaver();
|
||||
void ft_Scrollbar(i16 x, i16 y, i16 w, i16 h, u16 options, u16 val, u16 size, u16 range);
|
||||
void ft_SetBase(u32 base);
|
||||
void ft_SetBitmap(u32 source, u16 fmt, u16 w, u16 h);
|
||||
void ft_SetFont(u32 font, u32 ptr);
|
||||
void ft_SetFont2(u32 font, u32 ptr, u32 firstchar);
|
||||
void ft_SetMatrix();
|
||||
void ft_SetRotate(u32 r);
|
||||
void ft_SetScratch(u32 handle);
|
||||
void ft_Sketch(i16 x, i16 y, u16 w, u16 h, u32 ptr, u16 format);
|
||||
void ft_Slider(i16 x, i16 y, i16 w, i16 h, u16 options, u16 val, u16 range);
|
||||
void ft_Snapshot(u32 ptr);
|
||||
void ft_Snapshot2(u32 fmt, u32 ptr, i16 x, i16 y, i16 w, i16 h);
|
||||
void ft_Spinner(i16 x, i16 y, u16 style, u16 scale);
|
||||
void ft_Stop();
|
||||
void ft_Swap();
|
||||
void ft_Sync();
|
||||
void ft_Text(i16 x, i16 y, i16 font, u16 options, const char *s);
|
||||
void ft_Toggle(i16 x, i16 y, i16 w, i16 font, u16 options, u16 state, const char *s);
|
||||
void ft_TouchTransform(i32 x0, i32 y0, i32 x1, i32 y1, i32 x2, i32 y2, i32 tx0, i32 ty0, i32 tx1, i32 ty1, i32 tx2, i32 ty2, u16 result);
|
||||
void ft_Track(i16 x, i16 y, i16 w, i16 h, i16 tag);
|
||||
void ft_Translate(i32 tx, i32 ty);
|
||||
void ft_VideoFrame(u32 dst, u32 ptr);
|
||||
void ft_VideoStart();
|
||||
|
||||
// Display list
|
||||
void ft_AlphaFunc(u8 func, u8 ref);
|
||||
void ft_Begin(u8 prim);
|
||||
void ft_BitmapHandle(u8 handle);
|
||||
void ft_BitmapLayout(u8 format, u16 linestride, u16 height);
|
||||
void ft_BitmapSize(u8 filter, u8 wrapx, u8 wrapy, u16 width, u16 height);
|
||||
void ft_BitmapSource(u32 addr);
|
||||
void ft_BitmapTransformA(i32 a);
|
||||
void ft_BitmapTransformB(i32 b);
|
||||
void ft_BitmapTransformC(i32 c);
|
||||
void ft_BitmapTransformD(i32 d);
|
||||
void ft_BitmapTransformE(i32 e);
|
||||
void ft_BitmapTransformF(i32 f);
|
||||
void ft_BlendFunc(u8 src, u8 dst);
|
||||
void ft_Call(u16 dest);
|
||||
void ft_Cell(u8 cell);
|
||||
void ft_Clear(u8 c, u8 s, u8 t);
|
||||
void ft_ClearAll();
|
||||
void ft_ClearColorA(u8 alpha);
|
||||
void ft_ClearColorRGB(u8 red, u8 green, u8 blue);
|
||||
void ft_ClearColorRGB32(u32 rgb);
|
||||
void ft_ClearStencil(u8 s);
|
||||
void ft_ClearTag(u8 s);
|
||||
void ft_ColorA(u8 alpha);
|
||||
void ft_ColorMask(u8 r, u8 g, u8 b, u8 a);
|
||||
void ft_ColorRGB(u8 red, u8 green, u8 blue);
|
||||
void ft_ColorRGB32(u32 rgb);
|
||||
void ft_Display();
|
||||
void ft_End();
|
||||
void ft_Jump(u16 dest);
|
||||
void ft_LineWidth(u16 width);
|
||||
void ft_Macro(u8 m);
|
||||
void ft_PaletteSource(u32 addr);
|
||||
void ft_PointSize(u16 size);
|
||||
void ft_RestoreContext();
|
||||
void ft_Return();
|
||||
void ft_SaveContext();
|
||||
void ft_ScissorSize(u16 width, u16 height);
|
||||
void ft_ScissorXY(u16 x, u16 y);
|
||||
void ft_StencilFunc(u8 func, u8 ref, u8 mask);
|
||||
void ft_StencilMask(u8 mask);
|
||||
void ft_StencilOp(u8 sfail, u8 spass);
|
||||
void ft_Tag(u8 s);
|
||||
void ft_TagMask(u8 mask);
|
||||
void ft_Vertex2f(i16 x, i16 y);
|
||||
void ft_Vertex2ii(u16 x, u16 y, u8 handle, u8 cell);
|
||||
void ft_VertexFormat(u8 f);
|
||||
void ft_VertexTranslateX(i32 v);
|
||||
void ft_VertexTranslateY(i32 v);
|
||||
|
||||
// Common
|
||||
void init_ft8xx();
|
||||
|
||||
// Transport / mode switch
|
||||
esp_err_t ft_host_begin();
|
||||
esp_err_t ft_host_end();
|
||||
|
||||
// Command buffer helpers
|
||||
void ft_ccmd_start(void *addr);
|
||||
void ft_ccmd(u32 a);
|
||||
void ft_cstr(const char *s);
|
||||
esp_err_t ft_ccmd_write();
|
||||
|
||||
// RAM / register access
|
||||
u8 ft_rreg8(u32 a);
|
||||
u16 ft_rreg16(u32 a);
|
||||
u32 ft_rreg32(u32 a);
|
||||
|
||||
void ft_wreg8(u32 a, u8 v);
|
||||
void ft_wreg16(u32 a, u16 v);
|
||||
void ft_wreg32(u32 a, u32 v);
|
||||
|
||||
esp_err_t ft_open_session();
|
||||
esp_err_t ft_close_session();
|
||||
esp_err_t ft_read(void *addr, u32 ft_addr, u32 size);
|
||||
esp_err_t ft_write(const void *addr, u32 ft_addr, u32 size);
|
||||
esp_err_t ft_write_dl(const void *addr, u32 size_dwords);
|
||||
|
||||
// Host commands
|
||||
esp_err_t ft_cmd(u8 a);
|
||||
esp_err_t ft_cmdp(u8 a, u8 v);
|
||||
|
||||
// Coprocessor helpers
|
||||
esp_err_t ft_cp_wait(uint32_t timeout_ms);
|
||||
esp_err_t ft_cp_reset();
|
||||
|
||||
// Display / init
|
||||
esp_err_t ft_set_mode(u8 mode);
|
||||
void ft_swap();
|
||||
esp_err_t ft_wait_swap(uint32_t timeout_ms);
|
||||
|
||||
extern const FT_MODE ft_modes[];
|
||||
|
||||
// Math
|
||||
i16 rcos(i16 r, u16 th);
|
||||
i16 rsin(i16 r, u16 th);
|
||||
|
||||
extern const u16 sintab[];
|
||||
extern u32 *ft_ccmdb;
|
||||
extern u16 ft_ccmdp;
|
||||
|
||||
|
||||
// Text attribute display backend
|
||||
void ft_text_attr_mode_get_size(uint16_t *cols, uint16_t *rows);
|
||||
esp_err_t ft_text_attr_mode_begin();
|
||||
esp_err_t ft_text_attr_mode_show();
|
||||
esp_err_t ft_text_attr_mode_clear(u8 ch, u8 attr);
|
||||
esp_err_t ft_text_attr_mode_present(const u8 *chars, const u8 *attrs);
|
||||
void ft_text_attr_mode_end();
|
||||
|
||||
|
||||
// JPEG / RGB888 and DXP viewer helpers
|
||||
int ft_jpg_ext_match(const char *name);
|
||||
int ft_dxp_ext_match(const char *name);
|
||||
esp_err_t ft_jpg_show_file_mode(const char *path, bool stretch, bool quiet = false);
|
||||
esp_err_t ft_jpg_show_file(const char *path);
|
||||
esp_err_t ft_dxp_show_file_mode(const char *path, bool quiet = false);
|
||||
esp_err_t ft_dxp_show_file(const char *path);
|
||||
esp_err_t ft_rgb888_640x480_show_current(bool stretch);
|
||||
|
||||
// Console
|
||||
void ft_console_register_system_commands();
|
||||
@@ -0,0 +1,252 @@
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_heap_caps.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "esp_spi_defs.h"
|
||||
#include "spi_slave.h"
|
||||
#include "mem_obj.h"
|
||||
#include "helper.h"
|
||||
#include "gopher_client.h"
|
||||
|
||||
static const char *TAG = "gopher";
|
||||
|
||||
#define GOPHER_BUF_SIZE (2 * 1024 * 1024)
|
||||
|
||||
static uint8_t *gopher_buf = NULL;
|
||||
static size_t gopher_buf_size = 0;
|
||||
static int gopher_sock = -1;
|
||||
|
||||
void gopher_buf_free()
|
||||
{
|
||||
if (!gopher_buf) return;
|
||||
|
||||
heap_caps_free(gopher_buf);
|
||||
gopher_buf = NULL;
|
||||
gopher_buf_size = 0;
|
||||
ESP_LOGI(TAG, "buffer freed");
|
||||
}
|
||||
|
||||
bool gopher_buf_alloc()
|
||||
{
|
||||
if (gopher_buf) return true;
|
||||
|
||||
gopher_buf = (uint8_t*)heap_caps_malloc(GOPHER_BUF_SIZE, MALLOC_CAP_SPIRAM);
|
||||
if (!gopher_buf)
|
||||
{
|
||||
ESP_LOGW(TAG, "SPIRAM buffer alloc failed, size=%u", (unsigned)GOPHER_BUF_SIZE);
|
||||
return false;
|
||||
}
|
||||
|
||||
gopher_buf_size = GOPHER_BUF_SIZE;
|
||||
ESP_LOGI(TAG, "buffer allocated in SPIRAM: %p, size=%u", gopher_buf, (unsigned)gopher_buf_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
void gopher_close_socket()
|
||||
{
|
||||
if (gopher_sock < 0) return;
|
||||
|
||||
close(gopher_sock);
|
||||
gopher_sock = -1;
|
||||
}
|
||||
|
||||
void gopher_cleanup()
|
||||
{
|
||||
gopher_close_socket();
|
||||
gopher_buf_free();
|
||||
}
|
||||
|
||||
void gopher_init()
|
||||
{
|
||||
gopher_buf = NULL;
|
||||
gopher_buf_size = 0;
|
||||
gopher_sock = -1;
|
||||
|
||||
ESP_LOGI(TAG, "init done");
|
||||
}
|
||||
|
||||
void gopher_do_get()
|
||||
{
|
||||
ESP_LOGI(TAG, "GOPHER_GET");
|
||||
|
||||
if (!gopher_buf_alloc())
|
||||
{
|
||||
wr_reg8(ESP_REG_STATUS, 0xA0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!net.url[0])
|
||||
{
|
||||
ESP_LOGW(TAG, "URL not set");
|
||||
gopher_cleanup();
|
||||
wr_reg8(ESP_REG_STATUS, 0x81);
|
||||
return;
|
||||
}
|
||||
|
||||
char * url = (char*)net.url;
|
||||
|
||||
if (strncmp(url, "gopher://", 9) != 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "invalid URL format");
|
||||
gopher_cleanup();
|
||||
wr_reg8(ESP_REG_STATUS, 0x81);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "GET %s", url);
|
||||
|
||||
char host[256];
|
||||
int port = 70;
|
||||
char selector[256];
|
||||
|
||||
memset(host, 0, sizeof(host));
|
||||
memset(selector, 0, sizeof(selector));
|
||||
selector[0] = '\0';
|
||||
|
||||
const char * p = url + 9;
|
||||
const char * slash = strchr(p, '/');
|
||||
const char * colon = strchr(p, ':');
|
||||
|
||||
if (colon && (!slash || colon < slash))
|
||||
{
|
||||
int host_len = colon - p;
|
||||
strncpy(host, p, host_len);
|
||||
host[host_len] = 0;
|
||||
port = atoi(colon + 1);
|
||||
|
||||
if (slash)
|
||||
{
|
||||
const char * sel_start = slash + 1;
|
||||
|
||||
if (sel_start[0] && sel_start[1] == '/' && strchr("0123456789+gIihs", sel_start[0]))
|
||||
sel_start += 2;
|
||||
|
||||
strncpy(selector, sel_start, sizeof(selector) - 1);
|
||||
selector[sizeof(selector) - 1] = 0;
|
||||
}
|
||||
}
|
||||
else if (slash)
|
||||
{
|
||||
int host_len = slash - p;
|
||||
strncpy(host, p, host_len);
|
||||
host[host_len] = 0;
|
||||
const char * sel_start = slash + 1;
|
||||
|
||||
if (sel_start[0] && sel_start[1] == '/' && strchr("0123456789+gIihs", sel_start[0]))
|
||||
sel_start += 2;
|
||||
|
||||
strncpy(selector, sel_start, sizeof(selector) - 1);
|
||||
selector[sizeof(selector) - 1] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
strncpy(host, p, sizeof(host) - 1);
|
||||
host[sizeof(host) - 1] = 0;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "host=%s port=%d selector=%s", host, port, selector);
|
||||
|
||||
struct hostent * server = gethostbyname(host);
|
||||
if (!server)
|
||||
{
|
||||
ESP_LOGW(TAG, "DNS lookup failed");
|
||||
gopher_cleanup();
|
||||
wr_reg8(ESP_REG_STATUS, 0xB1);
|
||||
return;
|
||||
}
|
||||
|
||||
gopher_sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (gopher_sock < 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "socket creation failed");
|
||||
gopher_cleanup();
|
||||
wr_reg8(ESP_REG_STATUS, 0xB2);
|
||||
return;
|
||||
}
|
||||
|
||||
struct sockaddr_in serv_addr;
|
||||
memset(&serv_addr, 0, sizeof(serv_addr));
|
||||
serv_addr.sin_family = AF_INET;
|
||||
serv_addr.sin_port = htons(port);
|
||||
memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length);
|
||||
|
||||
if (connect(gopher_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "connection failed");
|
||||
gopher_cleanup();
|
||||
wr_reg8(ESP_REG_STATUS, 0xB2);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "connected");
|
||||
|
||||
char request[512];
|
||||
memset(request, 0, sizeof(request));
|
||||
snprintf(request, sizeof(request), "/%s\r\n", selector);
|
||||
|
||||
int sent = write(gopher_sock, request, strlen(request));
|
||||
if (sent < 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "send failed");
|
||||
gopher_cleanup();
|
||||
wr_reg8(ESP_REG_STATUS, 0xB2);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "sent %d bytes", sent);
|
||||
|
||||
int total = 0;
|
||||
int len;
|
||||
int last_progress = 0;
|
||||
|
||||
while ((len = read(gopher_sock, (char*)gopher_buf + total, gopher_buf_size - total)) > 0)
|
||||
{
|
||||
total += len;
|
||||
if ((size_t)total >= gopher_buf_size) break;
|
||||
|
||||
if (total - last_progress >= 65536)
|
||||
{
|
||||
wr_reg32(ESP_REG_DATA_SIZE, total);
|
||||
last_progress = total;
|
||||
}
|
||||
|
||||
vTaskDelay(1);
|
||||
}
|
||||
|
||||
gopher_close_socket();
|
||||
|
||||
ESP_LOGI(TAG, "recv %d bytes total", total);
|
||||
|
||||
if (total == 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "empty response");
|
||||
gopher_buf_free();
|
||||
wr_reg8(ESP_REG_STATUS, 0xB4);
|
||||
return;
|
||||
}
|
||||
|
||||
int resp = make_obj(total, OBJ_TYPE_DATA);
|
||||
if (resp < 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "make_obj failed");
|
||||
gopher_buf_free();
|
||||
wr_reg8(ESP_REG_STATUS, 0xA0);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(mem_obj[resp].addr, gopher_buf, total);
|
||||
gopher_buf_free();
|
||||
|
||||
wr_reg8(ESP_REG_OBJ_HANDLE, resp);
|
||||
wr_reg32(ESP_REG_DATA_SIZE, total);
|
||||
wr_reg8(ESP_REG_STATUS, ESP_ST_READY);
|
||||
|
||||
ESP_LOGI(TAG, "done: handle=%d size=%d", resp, total);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
void gopher_init(void);
|
||||
void gopher_do_get(void);
|
||||
@@ -0,0 +1,806 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "esp_spi_defs.h"
|
||||
#include "miniz.h"
|
||||
#include "spi_slave.h"
|
||||
#include "mem_obj.h"
|
||||
#include "elf.cpp.h"
|
||||
#include "depack.h"
|
||||
|
||||
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
|
||||
#include "wifi.h"
|
||||
#include "http_client.h"
|
||||
#include "weather.h"
|
||||
#include "gopher_client.h"
|
||||
#include "stream_client.h"
|
||||
#endif
|
||||
|
||||
#include "helper.h"
|
||||
#include "tracker.h"
|
||||
#include "sfx.h"
|
||||
#include "vfs_bridge.h"
|
||||
|
||||
const char TAG[] = "helper";
|
||||
|
||||
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
|
||||
EXT_RAM_BSS_ATTR u8 url_buf[1024];
|
||||
#endif
|
||||
|
||||
QueueHandle_t helper_queue;
|
||||
|
||||
NET net;
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "miniz.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
void IRAM_ATTR put_helper_isr(int task)
|
||||
{
|
||||
xQueueSendFromISR(helper_queue, &task, 0);
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
|
||||
void *f_malloc(size_t x)
|
||||
{
|
||||
#ifdef VERBOSE
|
||||
printf("f_malloc %u\r\n", x);
|
||||
#endif
|
||||
|
||||
return malloc_spiram(x);
|
||||
}
|
||||
|
||||
void f_free(void *p)
|
||||
{
|
||||
#ifdef VERBOSE
|
||||
printf("f_free\r\n");
|
||||
#endif
|
||||
|
||||
free(p);
|
||||
}
|
||||
|
||||
void *f_realloc(void *p, size_t x)
|
||||
{
|
||||
#ifdef VERBOSE
|
||||
printf("f_realloc %u\r\n", x);
|
||||
#endif
|
||||
|
||||
auto m = malloc_spiram(x);
|
||||
memcpy(m, p, x);
|
||||
free(p);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
u8 sfx_status_from_err(esp_err_t err)
|
||||
{
|
||||
switch (err)
|
||||
{
|
||||
case ESP_OK:
|
||||
return ESP_ST_READY;
|
||||
|
||||
case ESP_ERR_INVALID_ARG:
|
||||
return ESP_ERR_INV_PARAM;
|
||||
|
||||
case ESP_ERR_INVALID_SIZE:
|
||||
return ESP_ERR_INV_SIZE;
|
||||
|
||||
case ESP_ERR_NOT_FOUND:
|
||||
return ESP_ERR_INV_STATE;
|
||||
|
||||
case ESP_ERR_NOT_SUPPORTED:
|
||||
case ESP_ERR_INVALID_RESPONSE:
|
||||
return ESP_ERR_INV_PARAM;
|
||||
|
||||
case ESP_ERR_NO_MEM:
|
||||
return ESP_ERR_OUT_OF_HANDLES;
|
||||
|
||||
default:
|
||||
return ESP_ERR_INV_STATE;
|
||||
}
|
||||
}
|
||||
|
||||
void helper_task(void *arg)
|
||||
{
|
||||
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
|
||||
net.url = url_buf;
|
||||
memset(net.url, 0, 1024);
|
||||
#endif
|
||||
|
||||
while (1)
|
||||
{
|
||||
int task;
|
||||
|
||||
if (xQueueReceive(helper_queue, &task, portMAX_DELAY))
|
||||
{
|
||||
switch (task)
|
||||
{
|
||||
case TASK_RUN_FUNC0:
|
||||
case TASK_RUN_FUNC1:
|
||||
case TASK_RUN_FUNC2:
|
||||
case TASK_RUN_FUNC3:
|
||||
{
|
||||
int handle = rd_reg8(ESP_REG_LIB_HANDLE);
|
||||
int func = rd_reg8(ESP_REG_FUNC);
|
||||
int arg = rd_reg32(ESP_REG_ARG);
|
||||
|
||||
if (!check_handle(handle) || (mem_obj[handle].type != OBJ_TYPE_LIB))
|
||||
{
|
||||
set_status(ESP_ERR_INV_LIB_HANDLE);
|
||||
break;
|
||||
}
|
||||
|
||||
int arr1h = 0;
|
||||
int arr2h = 0;
|
||||
int arr3h = 0;
|
||||
|
||||
if (task >= TASK_RUN_FUNC1)
|
||||
{
|
||||
arr1h = rd_reg8(ESP_REG_ARR1_HANDLE);
|
||||
|
||||
if (!check_handle(arr1h) || ((mem_obj[arr1h].type != OBJ_TYPE_DATA) && (mem_obj[arr1h].type != OBJ_TYPE_DATAF)))
|
||||
{
|
||||
set_status(ESP_ERR_INV_ARG_HANDLE);
|
||||
#ifdef VERBOSE
|
||||
printf("Handle error, arr1h %d, type %d\r\n", arr1h, mem_obj[arr1h].type);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (task >= TASK_RUN_FUNC2)
|
||||
{
|
||||
arr2h = rd_reg8(ESP_REG_ARR2_HANDLE);
|
||||
|
||||
if (!check_handle(arr2h) || ((mem_obj[arr2h].type != OBJ_TYPE_DATA) && (mem_obj[arr2h].type != OBJ_TYPE_DATAF)))
|
||||
{
|
||||
set_status(ESP_ERR_INV_ARG_HANDLE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (task == TASK_RUN_FUNC3)
|
||||
{
|
||||
arr3h = rd_reg8(ESP_REG_ARR3_HANDLE);
|
||||
|
||||
if (!check_handle(arr3h) || ((mem_obj[arr3h].type != OBJ_TYPE_DATA) && (mem_obj[arr3h].type != OBJ_TYPE_DATAF)))
|
||||
{
|
||||
set_status(ESP_ERR_INV_ARG_HANDLE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void *entry = mem_obj[handle].entry;
|
||||
void *arr1 = mem_obj[arr1h].addr;
|
||||
void *arr2 = mem_obj[arr2h].addr;
|
||||
void *arr3 = mem_obj[arr3h].addr;
|
||||
int rc = 0;
|
||||
|
||||
switch (task)
|
||||
{
|
||||
case TASK_RUN_FUNC0:
|
||||
{
|
||||
typedef int (*F0)(int func, int arg);
|
||||
F0 f = (F0)entry;
|
||||
rc = f(func, arg);
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_RUN_FUNC1:
|
||||
{
|
||||
typedef int (*F1)(int func, int arg, void *arr1);
|
||||
F1 f = (F1)entry;
|
||||
rc = f(func, arg, arr1);
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_RUN_FUNC2:
|
||||
{
|
||||
typedef int (*F2)(int func, int arg, void *arr1, void *arr2);
|
||||
F2 f = (F2)entry;
|
||||
rc = f(func, arg, arr1, arr2);
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_RUN_FUNC3:
|
||||
{
|
||||
typedef int (*F3)(int func, int arg, void *arr1, void *arr2, void *arr3);
|
||||
F3 f = (F3)entry;
|
||||
rc = f(func, arg, arr1, arr2, arr3);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef VERBOSE
|
||||
printf("RC: 0x%08lX\r\n", (u32)rc);
|
||||
#endif
|
||||
|
||||
wr_reg32(ESP_REG_RETVAL, rc);
|
||||
|
||||
set_status(ESP_ST_READY);
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_LOAD_ELF:
|
||||
case TASK_LOAD_ELF_OPT:
|
||||
{
|
||||
int handle = rd_reg8(ESP_REG_OBJ_HANDLE);
|
||||
int opt = (task == TASK_LOAD_ELF_OPT) ? rd_reg8(ESP_REG_OPT) : 0;
|
||||
|
||||
if (!check_handle(handle) || (mem_obj[handle].type != OBJ_TYPE_ELF))
|
||||
{
|
||||
set_status(ESP_ERR_INV_ELF_HANDLE);
|
||||
break;
|
||||
}
|
||||
|
||||
int new_size = load_elf((u8*)mem_obj[handle].addr, &mem_obj[handle], opt);
|
||||
|
||||
if (new_size == -1)
|
||||
set_status(ESP_ERR_OUT_OF_MEMORY);
|
||||
else
|
||||
{
|
||||
// Delete ELF from memory and change object type to library
|
||||
if (mem_obj[handle].addr) free(mem_obj[handle].addr);
|
||||
|
||||
mem_obj[handle].addr = (void*)-1;
|
||||
mem_obj[handle].size = new_size;
|
||||
mem_obj[handle].type = OBJ_TYPE_LIB;
|
||||
mem_obj[handle].state = LIB_OBJ_ST_READY;
|
||||
wr_reg8(ESP_REG_LIB_HANDLE, handle);
|
||||
|
||||
set_status(ESP_ST_READY);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_MAKE_OBJ:
|
||||
{
|
||||
size_t obj_size = rd_reg32(ESP_REG_DATA_SIZE);
|
||||
int obj_type = rd_reg8(ESP_REG_OBJ_TYPE);
|
||||
|
||||
int obj_hdl = make_obj(obj_size, obj_type); // set_status is done there
|
||||
|
||||
if (obj_hdl >= 0)
|
||||
{
|
||||
wr_reg8(ESP_REG_OBJ_HANDLE, obj_hdl);
|
||||
wr_reg32(ESP_REG_DATA_OFFSET, 0);
|
||||
wr_reg32(ESP_REG_DATA_SIZE, min(obj_size, DMA_BUF_SIZE));
|
||||
|
||||
set_status(ESP_ST_READY);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_DEL_OBJ:
|
||||
{
|
||||
int handle = rd_reg8(ESP_REG_OBJ_HANDLE);
|
||||
|
||||
if (!check_handle(handle))
|
||||
{
|
||||
set_status(ESP_ERR_INV_HANDLE);
|
||||
ESP_LOGE(TAG, "Wrong handle! (%02X)", handle);
|
||||
break;
|
||||
}
|
||||
|
||||
auto rc = delete_obj(handle);
|
||||
|
||||
if (!rc)
|
||||
{
|
||||
set_status(ESP_ERR_OBJ_NOT_DELETED);
|
||||
ESP_LOGE(TAG, "Object cannot be deleted! (%02X)", handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
set_status(ESP_ST_READY);
|
||||
|
||||
#ifdef VERBOSE
|
||||
printf("Object deleted: handle %02X\r\n", handle);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_KILL_OBJ:
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
int playing = -1;
|
||||
|
||||
for (int i = 0; i < OBJ_HANDLES_MAX; i++)
|
||||
{
|
||||
if (mem_obj[i].addr && (mem_obj[i].type == OBJ_TYPE_XMC || mem_obj[i].type == OBJ_TYPE_MDC || mem_obj[i].type == OBJ_TYPE_S3C) && mem_obj[i].state == TRACK_OBJ_ST_PLAYING)
|
||||
{
|
||||
playing = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (playing < 0) break;
|
||||
|
||||
TRACK_TASK t = {};
|
||||
t.task = TRACK_TASK_STOP;
|
||||
t.handle = playing;
|
||||
xQueueSend(xm_queue, &t, portMAX_DELAY);
|
||||
|
||||
while (mem_obj[playing].addr && mem_obj[playing].state == TRACK_OBJ_ST_PLAYING)
|
||||
vTaskDelay(1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < OBJ_HANDLES_MAX; i++)
|
||||
{
|
||||
if (check_handle(i))
|
||||
{
|
||||
auto rc = delete_obj(i);
|
||||
|
||||
if (!rc)
|
||||
ESP_LOGE(TAG, "Object cannot be deleted! (%02X)", i);
|
||||
#ifdef VERBOSE
|
||||
else
|
||||
printf("Object deleted: handle %02X\r\n", i);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
set_status(ESP_ST_READY);
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_DEHST:
|
||||
{
|
||||
int hsrc = rd_reg8(ESP_REG_OBJ_HANDLE);
|
||||
u32 ssize = rd_reg32(ESP_REG_DATA_SIZE);
|
||||
int dtype = rd_reg8(ESP_REG_OBJ_TYPE);
|
||||
|
||||
if (!check_handle(hsrc))
|
||||
{
|
||||
set_status(ESP_ERR_INV_HST_HANDLE);
|
||||
break;
|
||||
}
|
||||
|
||||
if (mem_obj[hsrc].type != OBJ_TYPE_HST)
|
||||
{
|
||||
set_status(ESP_ERR_INV_OBJ_TYPE);
|
||||
break;
|
||||
}
|
||||
|
||||
// Check source size
|
||||
if (!ssize || (ssize > 65536))
|
||||
{
|
||||
set_status(ESP_ERR_INV_SIZE);
|
||||
break;
|
||||
}
|
||||
|
||||
// Unpack the HST
|
||||
u8 *tmp = (u8*)malloc_spiram(65536);
|
||||
|
||||
if (!tmp)
|
||||
{
|
||||
set_status(ESP_ERR_OUT_OF_MEMORY);
|
||||
break;
|
||||
}
|
||||
|
||||
u32 dsize = dehrust(tmp, (u8*)mem_obj[hsrc].addr, ssize);
|
||||
|
||||
if (!dsize)
|
||||
{
|
||||
free(tmp);
|
||||
set_status(ESP_ERR_INV_HST);
|
||||
break;
|
||||
}
|
||||
|
||||
void *dst = malloc_spiram(dsize);
|
||||
|
||||
if (!dst)
|
||||
{
|
||||
free(tmp);
|
||||
set_status(ESP_ERR_OUT_OF_MEMORY);
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(dst, tmp, dsize);
|
||||
free(tmp);
|
||||
free(mem_obj[hsrc].addr);
|
||||
|
||||
mem_obj[hsrc].addr = dst;
|
||||
mem_obj[hsrc].size = dsize;
|
||||
mem_obj[hsrc].type = dtype;
|
||||
|
||||
wr_reg32(ESP_REG_DATA_OFFSET, 0);
|
||||
wr_reg32(ESP_REG_DATA_SIZE, min(dsize, DMA_BUF_SIZE));
|
||||
|
||||
set_status(ESP_ST_READY);
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_UNZIP:
|
||||
{
|
||||
int hsrc = rd_reg8(ESP_REG_OBJ_HANDLE);
|
||||
size_t ssize = rd_reg32(ESP_REG_DATA_SIZE);
|
||||
int dtype = rd_reg8(ESP_REG_OBJ_TYPE);
|
||||
|
||||
if (!check_handle(hsrc))
|
||||
{
|
||||
set_status(ESP_ERR_INV_HST_HANDLE);
|
||||
break;
|
||||
}
|
||||
|
||||
if (mem_obj[hsrc].type != OBJ_TYPE_ZIP)
|
||||
{
|
||||
set_status(ESP_ERR_INV_OBJ_TYPE);
|
||||
break;
|
||||
}
|
||||
|
||||
auto src = (u8*)mem_obj[hsrc].addr;
|
||||
size_t dsize = *(u32*)src;
|
||||
void *dst = (u8*)malloc_spiram(dsize);
|
||||
|
||||
if (!dst)
|
||||
{
|
||||
set_status(ESP_ERR_OUT_OF_MEMORY);
|
||||
break;
|
||||
}
|
||||
|
||||
tinfl_init(decomp);
|
||||
|
||||
ssize -= 4;
|
||||
tinfl_status decomp_status = tinfl_decompress(decomp, &src[4], &ssize, (u8*)dst, (u8*)dst, &dsize, TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
|
||||
|
||||
#ifdef VERBOSE
|
||||
printf("Unzip rc = %d, inbytes = %d, outbytes = %d\r\n", decomp_status, ssize, dsize);
|
||||
#endif
|
||||
|
||||
if (decomp_status != TINFL_STATUS_DONE)
|
||||
{
|
||||
set_status(ESP_ERR_INV_ZIP);
|
||||
break;
|
||||
}
|
||||
|
||||
mem_obj[hsrc].addr = dst;
|
||||
mem_obj[hsrc].size = dsize;
|
||||
mem_obj[hsrc].type = dtype;
|
||||
|
||||
wr_reg32(ESP_REG_DATA_OFFSET, 0);
|
||||
wr_reg32(ESP_REG_DATA_SIZE, min(dsize, DMA_BUF_SIZE));
|
||||
|
||||
set_status(ESP_ST_READY);
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_RESET_STREAM_UNZIP:
|
||||
process_rx_data(DREQ_ZIP, 0); // Reset stream depacker
|
||||
break;
|
||||
|
||||
case TASK_XM_STREAM_LOAD:
|
||||
xm_host_stream_prepare_command(rd_reg32(ESP_REG_DATA_OFFSET), rd_reg32(ESP_REG_DATA_SIZE));
|
||||
break;
|
||||
|
||||
case TASK_MOD_STREAM_LOAD:
|
||||
mod_host_stream_prepare_command(rd_reg32(ESP_REG_DATA_OFFSET), rd_reg32(ESP_REG_DATA_SIZE));
|
||||
break;
|
||||
|
||||
case TASK_S3M_STREAM_LOAD:
|
||||
s3m_host_stream_prepare_command(rd_reg32(ESP_REG_DATA_OFFSET), rd_reg32(ESP_REG_DATA_SIZE));
|
||||
break;
|
||||
|
||||
case TASK_TRACK_STREAM_BREAK:
|
||||
xm_host_stream_abort_current();
|
||||
set_status(ESP_ST_READY);
|
||||
break;
|
||||
|
||||
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
|
||||
case TASK_WSCAN:
|
||||
{
|
||||
if (net.is_busy)
|
||||
{
|
||||
set_status(ESP_ERR_NET_BUSY);
|
||||
break;
|
||||
}
|
||||
|
||||
net.is_busy = true;
|
||||
esp_err_t wscan_err = (esp_err_t)wf_scan_locked(300 /* timeout */);
|
||||
net.is_busy = false;
|
||||
|
||||
if (wscan_err != ESP_OK)
|
||||
{
|
||||
printf("E: WiFi scan failed: %s\r\n", esp_err_to_name(wscan_err));
|
||||
set_status(ESP_ERR_INV_STATE);
|
||||
break;
|
||||
}
|
||||
|
||||
put_txq(DREQ_WSCAN);
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_CONN:
|
||||
{
|
||||
if (net.is_busy)
|
||||
{
|
||||
set_status(ESP_ERR_NET_BUSY);
|
||||
break;
|
||||
}
|
||||
|
||||
net.is_busy = true;
|
||||
net.state = NETWORK_OPENING;
|
||||
auto rc = wifi_connect_locked((const char*)net.ssid, (const char*)net.pwd, 10000);
|
||||
net.is_busy = false;
|
||||
net.state = rc ? NETWORK_OPEN : NETWORK_CLOSED;
|
||||
|
||||
if (rc)
|
||||
{
|
||||
get_ip(net.ip.own_ip, net.ip.mask, net.ip.gate);
|
||||
set_status(ESP_ST_READY);
|
||||
}
|
||||
else
|
||||
set_status(ESP_ERR_AP_NOT_CONNECTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_DISCONN:
|
||||
{
|
||||
wifi_disconnect_now_locked();
|
||||
net.state = NETWORK_CLOSED;
|
||||
set_status(ESP_ST_READY);
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_HTTP_GET:
|
||||
http_do_get();
|
||||
break;
|
||||
|
||||
case TASK_WEATHER_GET:
|
||||
weather_do_get();
|
||||
break;
|
||||
|
||||
case TASK_HTTPS_GET:
|
||||
https_do_get();
|
||||
break;
|
||||
|
||||
case TASK_GOPHER_GET:
|
||||
gopher_do_get();
|
||||
break;
|
||||
|
||||
case TASK_HTTP_STREAM_START:
|
||||
stream_http_start();
|
||||
break;
|
||||
|
||||
case TASK_HTTPS_STREAM_START:
|
||||
stream_https_start();
|
||||
break;
|
||||
|
||||
case TASK_GOPHER_STREAM_START:
|
||||
stream_gopher_start();
|
||||
break;
|
||||
|
||||
case TASK_STREAM_READ:
|
||||
stream_read();
|
||||
break;
|
||||
|
||||
case TASK_STREAM_CLOSE:
|
||||
stream_close();
|
||||
break;
|
||||
|
||||
case TASK_HTTP_STREAM_READ:
|
||||
http_stream_read_task();
|
||||
break;
|
||||
#else
|
||||
case TASK_WSCAN:
|
||||
case TASK_CONN:
|
||||
case TASK_DISCONN:
|
||||
case TASK_HTTP_GET:
|
||||
case TASK_WEATHER_GET:
|
||||
case TASK_HTTPS_GET:
|
||||
case TASK_GOPHER_GET:
|
||||
case TASK_HTTP_STREAM_START:
|
||||
case TASK_HTTPS_STREAM_START:
|
||||
case TASK_GOPHER_STREAM_START:
|
||||
case TASK_STREAM_READ:
|
||||
case TASK_STREAM_CLOSE:
|
||||
case TASK_HTTP_STREAM_READ:
|
||||
set_status(ESP_ERR_INV_COMMAND);
|
||||
break;
|
||||
#endif
|
||||
|
||||
case TASK_SFX_PLAY:
|
||||
{
|
||||
int handle = rd_reg8(ESP_REG_OBJ_HANDLE);
|
||||
|
||||
if (!check_handle(handle) || mem_obj[handle].type != OBJ_TYPE_WAV)
|
||||
{
|
||||
set_status(ESP_ERR_INV_HANDLE);
|
||||
break;
|
||||
}
|
||||
|
||||
int channel = -1;
|
||||
esp_err_t err = sfx_play_sync(handle, rd_reg8(ESP_REG_SFX_GROUP), SFX_VOLUME_MAX, SFX_PAN_CENTER, SFX_PITCH_ONE, &channel);
|
||||
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
set_status(sfx_status_from_err(err));
|
||||
break;
|
||||
}
|
||||
|
||||
wr_reg8(ESP_REG_SFX_CHANNEL, channel);
|
||||
set_status(ESP_ST_READY);
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_SFX_PLAY_EX:
|
||||
{
|
||||
int handle = rd_reg8(ESP_REG_OBJ_HANDLE);
|
||||
|
||||
if (!check_handle(handle) || mem_obj[handle].type != OBJ_TYPE_WAV)
|
||||
{
|
||||
set_status(ESP_ERR_INV_HANDLE);
|
||||
break;
|
||||
}
|
||||
|
||||
u16 pitch = 0;
|
||||
rd_regs(ESP_REG_SFX_PITCH, &pitch, sizeof(pitch));
|
||||
|
||||
int channel = -1;
|
||||
esp_err_t err = sfx_play_sync(
|
||||
handle,
|
||||
rd_reg8(ESP_REG_SFX_GROUP),
|
||||
rd_reg8(ESP_REG_SFX_VOLUME),
|
||||
rd_reg8(ESP_REG_SFX_PAN),
|
||||
pitch,
|
||||
&channel);
|
||||
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
set_status(sfx_status_from_err(err));
|
||||
break;
|
||||
}
|
||||
|
||||
wr_reg8(ESP_REG_SFX_CHANNEL, channel);
|
||||
set_status(ESP_ST_READY);
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_SFX_STOP:
|
||||
{
|
||||
esp_err_t err = sfx_stop_sync(rd_reg8(ESP_REG_SFX_CHANNEL));
|
||||
set_status(sfx_status_from_err(err));
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_SFX_SET_PARAMS:
|
||||
{
|
||||
u16 pitch = 0;
|
||||
rd_regs(ESP_REG_SFX_PITCH, &pitch, sizeof(pitch));
|
||||
|
||||
esp_err_t err = sfx_set_params_sync(
|
||||
rd_reg8(ESP_REG_SFX_CHANNEL),
|
||||
rd_reg8(ESP_REG_SFX_VOLUME),
|
||||
rd_reg8(ESP_REG_SFX_PAN),
|
||||
pitch);
|
||||
|
||||
set_status(sfx_status_from_err(err));
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_SFX_SET_VOLUME:
|
||||
{
|
||||
esp_err_t err = sfx_set_volume_sync(rd_reg8(ESP_REG_SFX_VOLUME));
|
||||
set_status(sfx_status_from_err(err));
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_SFX_GET_STATE:
|
||||
{
|
||||
SFX_CHANNEL_STATE state = {};
|
||||
esp_err_t err = sfx_get_state_sync(rd_reg8(ESP_REG_SFX_CHANNEL), &state);
|
||||
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
set_status(sfx_status_from_err(err));
|
||||
break;
|
||||
}
|
||||
|
||||
wr_reg8(ESP_REG_SFX_STATE_ACTIVE, state.active);
|
||||
wr_reg8(ESP_REG_SFX_STATE_HANDLE, state.handle);
|
||||
wr_reg8(ESP_REG_SFX_STATE_GROUP, state.group);
|
||||
wr_reg8(ESP_REG_SFX_STATE_ORDER, state.group_order);
|
||||
wr_reg8(ESP_REG_SFX_STATE_VOLUME, state.volume);
|
||||
wr_reg8(ESP_REG_SFX_STATE_PAN, state.pan);
|
||||
wr_regs(ESP_REG_SFX_STATE_PITCH, &state.pitch, sizeof(state.pitch));
|
||||
wr_reg32(ESP_REG_SFX_STATE_RATE, state.sample_rate);
|
||||
wr_reg32(ESP_REG_SFX_STATE_FRAMES, state.frame_count);
|
||||
wr_reg32(ESP_REG_SFX_STATE_POSITION, state.position);
|
||||
wr_regs(ESP_REG_SFX_STATE_BITS, &state.bits_per_sample, sizeof(state.bits_per_sample));
|
||||
wr_reg8(ESP_REG_SFX_STATE_CHANNELS, state.channels);
|
||||
wr_reg8(ESP_REG_SFX_STATE_SIGNED, state.is_signed);
|
||||
set_status(ESP_ST_READY);
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_TRACK_GET_STATE:
|
||||
{
|
||||
int handle = rd_reg8(ESP_REG_OBJ_HANDLE);
|
||||
|
||||
if (!check_handle(handle) || (mem_obj[handle].type != OBJ_TYPE_XMC && mem_obj[handle].type != OBJ_TYPE_MDC && mem_obj[handle].type != OBJ_TYPE_S3C))
|
||||
{
|
||||
set_status(ESP_ERR_INV_XM_HANDLE);
|
||||
break;
|
||||
}
|
||||
|
||||
wr_reg8(ESP_REG_TRACK_STATUS, mem_obj[handle].state);
|
||||
wr_reg8(ESP_REG_TRACK_IS_CURRENT, curr_xm_handle == handle);
|
||||
set_status(ESP_ST_READY);
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_TRACK_GET_INFO:
|
||||
{
|
||||
int handle = rd_reg8(ESP_REG_OBJ_HANDLE);
|
||||
|
||||
if (!check_handle(handle) || !mem_obj[handle].addr || (mem_obj[handle].type != OBJ_TYPE_XMC && mem_obj[handle].type != OBJ_TYPE_MDC && mem_obj[handle].type != OBJ_TYPE_S3C))
|
||||
{
|
||||
set_status(ESP_ERR_INV_XM_HANDLE);
|
||||
break;
|
||||
}
|
||||
|
||||
xm_context_t *ctx = (xm_context_t*)mem_obj[handle].addr;
|
||||
uint8_t pattern_index = 0, pattern = 0, row = 0;
|
||||
uint64_t samples = 0;
|
||||
uint16_t bpm = 0, tempo = 0;
|
||||
|
||||
xm_get_position(ctx, &pattern_index, &pattern, &row, &samples);
|
||||
xm_get_playing_speed(ctx, &bpm, &tempo);
|
||||
|
||||
wr_reg8(ESP_REG_TRACK_STATUS, mem_obj[handle].state);
|
||||
wr_reg8(ESP_REG_TRACK_IS_CURRENT, curr_xm_handle == handle);
|
||||
wr_reg8(ESP_REG_TRACK_PATTERN_INDEX, pattern_index);
|
||||
wr_reg8(ESP_REG_TRACK_PATTERN, pattern);
|
||||
wr_reg8(ESP_REG_TRACK_ROW, row);
|
||||
wr_reg32(ESP_REG_TRACK_SAMPLES, (u32)samples);
|
||||
wr_regs(ESP_REG_TRACK_BPM, &bpm, sizeof(bpm));
|
||||
wr_regs(ESP_REG_TRACK_TEMPO, &tempo, sizeof(tempo));
|
||||
wr_reg8(ESP_REG_TRACK_LOOP_COUNT, xm_get_loop_count(ctx));
|
||||
wr_reg32(ESP_REG_TRACK_TOTAL_SAMPLES, xm_get_total_samples(handle));
|
||||
set_status(ESP_ST_READY);
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_SFX_STOP_GROUP:
|
||||
{
|
||||
esp_err_t err = sfx_stop_group_sync(rd_reg8(ESP_REG_SFX_GROUP));
|
||||
set_status(sfx_status_from_err(err));
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_REBOOT:
|
||||
ESP_LOGE(TAG, "software restart requested by SPI command REBOOT (0xED)");
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
esp_restart();
|
||||
break;
|
||||
|
||||
case TASK_RESET:
|
||||
ESP_LOGE(TAG, "software restart requested by SPI command RESET (0xEE)");
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
esp_restart();
|
||||
break;
|
||||
|
||||
case TASK_VFS_SERVER_START:
|
||||
vfs_bridge_start_server();
|
||||
set_status(ESP_ST_READY);
|
||||
break;
|
||||
|
||||
case TASK_VFS_SERVER_STOP:
|
||||
vfs_bridge_stop_server();
|
||||
set_status(ESP_ST_READY);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
enum
|
||||
{
|
||||
TASK_MAKE_OBJ,
|
||||
TASK_DEL_OBJ,
|
||||
TASK_KILL_OBJ,
|
||||
TASK_LOAD_ELF,
|
||||
TASK_LOAD_ELF_OPT,
|
||||
TASK_RUN_FUNC0,
|
||||
TASK_RUN_FUNC1,
|
||||
TASK_RUN_FUNC2,
|
||||
TASK_RUN_FUNC3,
|
||||
TASK_DEHST,
|
||||
TASK_UNZIP,
|
||||
TASK_RESET_STREAM_UNZIP,
|
||||
TASK_XM_STREAM_LOAD,
|
||||
TASK_MOD_STREAM_LOAD,
|
||||
TASK_S3M_STREAM_LOAD,
|
||||
TASK_TRACK_STREAM_BREAK,
|
||||
TASK_TRACK_GET_STATE,
|
||||
TASK_TRACK_GET_INFO,
|
||||
TASK_WSCAN,
|
||||
TASK_CONN,
|
||||
TASK_DISCONN,
|
||||
TASK_HTTP_GET,
|
||||
TASK_WEATHER_GET,
|
||||
TASK_HTTPS_GET,
|
||||
TASK_GOPHER_GET,
|
||||
TASK_HTTP_STREAM_START,
|
||||
TASK_HTTPS_STREAM_START,
|
||||
TASK_GOPHER_STREAM_START,
|
||||
TASK_STREAM_READ,
|
||||
TASK_STREAM_CLOSE,
|
||||
TASK_HTTP_STREAM_READ,
|
||||
TASK_SFX_PLAY,
|
||||
TASK_SFX_PLAY_EX,
|
||||
TASK_SFX_STOP,
|
||||
TASK_SFX_SET_PARAMS,
|
||||
TASK_SFX_SET_VOLUME,
|
||||
TASK_SFX_GET_STATE,
|
||||
TASK_SFX_STOP_GROUP,
|
||||
TASK_REBOOT,
|
||||
TASK_RESET,
|
||||
TASK_VFS_SERVER_START,
|
||||
TASK_VFS_SERVER_STOP,
|
||||
};
|
||||
|
||||
#pragma pack(1)
|
||||
struct NET
|
||||
{
|
||||
bool is_init = false;
|
||||
bool is_busy = false;
|
||||
u8 state;
|
||||
|
||||
u8 ssid[33];
|
||||
u8 pwd[62];
|
||||
u8 *url;
|
||||
|
||||
struct
|
||||
{
|
||||
u8 ip[4];
|
||||
u8 own_ip[4];
|
||||
u8 mask[4];
|
||||
u8 gate[4];
|
||||
} ip;
|
||||
};
|
||||
#pragma pack()
|
||||
|
||||
extern QueueHandle_t helper_queue;
|
||||
extern struct NET net;
|
||||
|
||||
void put_txq(int type);
|
||||
void put_txq_isr(int type);
|
||||
void put_rxq(int type);
|
||||
void put_rxq_isr(int type);
|
||||
void put_helper_isr(int task);
|
||||
|
||||
void helper_task(void *arg);
|
||||
|
||||
u32 prepare_tx_data(u8 type, size_t size);
|
||||
void process_rx_data(u8 type, size_t size);
|
||||
@@ -0,0 +1,184 @@
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_crt_bundle.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "esp_spi_defs.h"
|
||||
#include "spi_slave.h"
|
||||
#include "mem_obj.h"
|
||||
#include "helper.h"
|
||||
#include "http_client.h"
|
||||
|
||||
const char *TAG = "http";
|
||||
|
||||
#define HTTP_BUF_SIZE (2 * 1024 * 1024)
|
||||
|
||||
void http_init() {}
|
||||
|
||||
void http_do_get()
|
||||
{
|
||||
uint8_t *http_buf = NULL;
|
||||
uint8_t url_handle = rd_reg8(ESP_REG_OBJ_HANDLE);
|
||||
|
||||
char *url;
|
||||
int total = 0;
|
||||
int len;
|
||||
int code;
|
||||
|
||||
esp_http_client_handle_t client;
|
||||
esp_http_client_config_t cfg = {};
|
||||
|
||||
esp_err_t err;
|
||||
int resp;
|
||||
|
||||
ESP_LOGI(TAG, "HTTP_GET handle=%d", url_handle);
|
||||
|
||||
http_buf = (uint8_t*)heap_caps_malloc(HTTP_BUF_SIZE, MALLOC_CAP_SPIRAM);
|
||||
|
||||
if (!http_buf)
|
||||
{
|
||||
set_status(0xA0);
|
||||
ESP_LOGE(TAG, "HTTP buf malloc error");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!net.url[0])
|
||||
{
|
||||
ESP_LOGW(TAG, "URL not set");
|
||||
set_status(0x81);
|
||||
return;
|
||||
}
|
||||
|
||||
url = (char*)net.url;
|
||||
|
||||
if (strncmp(url, "http://", 7) != 0 && strncmp(url, "https://", 8) != 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "invalid URL format");
|
||||
set_status(0x81);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "GET %s", url);
|
||||
|
||||
cfg.url = url;
|
||||
cfg.timeout_ms = 30000;
|
||||
cfg.buffer_size = 4096;
|
||||
cfg.user_agent = "ZiFi/1.0";
|
||||
|
||||
// Enable TLS for HTTPS
|
||||
if (strncmp(url, "https://", 8) == 0)
|
||||
cfg.crt_bundle_attach = esp_crt_bundle_attach;
|
||||
|
||||
client = esp_http_client_init(&cfg);
|
||||
|
||||
if (!client)
|
||||
{
|
||||
ESP_LOGW(TAG, "init fail");
|
||||
set_status(0xB2);
|
||||
return;
|
||||
}
|
||||
|
||||
err = esp_http_client_open(client, 0);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "open fail");
|
||||
esp_http_client_cleanup(client);
|
||||
set_status(0xB2);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
esp_http_client_fetch_headers(client);
|
||||
code = esp_http_client_get_status_code(client);
|
||||
ESP_LOGI(TAG, "status %d", code);
|
||||
|
||||
if (code < 200 || code >= 300)
|
||||
{
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
set_status(0xB3);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
len = esp_http_client_read(client, (char*)http_buf + total, HTTP_BUF_SIZE - total);
|
||||
|
||||
if (len > 0)
|
||||
{
|
||||
total += len;
|
||||
|
||||
wr_reg32(ESP_REG_DATA_SIZE, total);
|
||||
ESP_LOGI(TAG, "got: %d bytes, progress: %d bytes", len, total);
|
||||
|
||||
if (total >= HTTP_BUF_SIZE) break;
|
||||
}
|
||||
|
||||
else if (len == -ESP_ERR_HTTP_EAGAIN)
|
||||
continue;
|
||||
|
||||
else if (len == 0)
|
||||
{
|
||||
ESP_LOGI(TAG, "empty response received");
|
||||
break;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "error: %d", -len);
|
||||
break;
|
||||
}
|
||||
|
||||
vTaskDelay(1);
|
||||
}
|
||||
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
|
||||
ESP_LOGI(TAG, "recv %d bytes", total);
|
||||
|
||||
if (total == 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "empty response");
|
||||
set_status(0xB4);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
resp = make_obj(total, OBJ_TYPE_DATA);
|
||||
if (resp < 0)
|
||||
{
|
||||
ESP_LOGE(TAG, "make_obj failed");
|
||||
set_status(0xA0);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
memcpy(mem_obj[resp].addr, http_buf, total);
|
||||
|
||||
wr_reg8(ESP_REG_OBJ_HANDLE, resp);
|
||||
wr_reg32(ESP_REG_DATA_SIZE, total);
|
||||
set_status(ESP_ST_READY);
|
||||
|
||||
ESP_LOGI(TAG, "done: handle=%d size=%d", resp, total);
|
||||
|
||||
cleanup:
|
||||
if (http_buf) free(http_buf);
|
||||
}
|
||||
|
||||
void https_do_get()
|
||||
{
|
||||
// HTTPS uses same logic as HTTP - esp_http_client handles both
|
||||
// Just need to ensure URL starts with https://
|
||||
http_do_get();
|
||||
}
|
||||
|
||||
void http_stream_read_task() {}
|
||||
void http_stream_delete(uint8_t handle) {}
|
||||
bool http_is_stream_handle(uint8_t handle) { return false; }
|
||||
void http_stream_read(uint8_t handle, uint32_t offset, uint32_t size) {}
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
void http_init(void);
|
||||
void http_do_get(void);
|
||||
void https_do_get(void);
|
||||
|
||||
// Streaming API
|
||||
void http_stream_read_task(void); // Вызывается из helper task
|
||||
void http_stream_delete(uint8_t handle);
|
||||
bool http_is_stream_handle(uint8_t handle);
|
||||
void http_stream_read(uint8_t handle, uint32_t offset, uint32_t size); // legacy
|
||||
@@ -0,0 +1,18 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
## Required IDF version
|
||||
idf:
|
||||
version: '>=4.1.0'
|
||||
# # Put list of dependencies here
|
||||
# # For components maintained by Espressif:
|
||||
# component: "~1.0.0"
|
||||
# # For 3rd party components:
|
||||
# username/component: ">=1.0.0,<2.0.0"
|
||||
# username2/component2:
|
||||
# version: "~1.0.0"
|
||||
# # For transient dependencies `public` flag can be set.
|
||||
# # `public` flag doesn't have an effect dependencies of the `main` component.
|
||||
# # All dependencies of `main` are public by default.
|
||||
# public: true
|
||||
espressif/usb_host_hid: ^1.1.0
|
||||
espressif/esp_new_jpeg: ^1.0.0
|
||||
@@ -0,0 +1,238 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
#include "bootloader_random.h"
|
||||
#include "esp_random.h"
|
||||
#include "nvs.h"
|
||||
#include "miniz.h"
|
||||
#include "nvs_flash.h"
|
||||
#include <esp_crc.h>
|
||||
|
||||
#include "main.h"
|
||||
#include "esp_spi_defs.h"
|
||||
#include "spi_slave.h"
|
||||
#include "console.h"
|
||||
#include "nvs_params.h"
|
||||
|
||||
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
|
||||
#include "wifi.h"
|
||||
#include "http_client.h"
|
||||
#include "gopher_client.h"
|
||||
#include "stream_client.h"
|
||||
#include "at_uart.h"
|
||||
#include "at_cip.h"
|
||||
#include "vfs_bridge.h"
|
||||
#include "vfs_request.h"
|
||||
#endif
|
||||
|
||||
#include "mem_obj.h"
|
||||
#include "tracker.h"
|
||||
#include "stats.h"
|
||||
#include "ft8xx.h"
|
||||
#include "elf.h"
|
||||
#include "sdmmc.h"
|
||||
#include "helper.h"
|
||||
#include "depack.h"
|
||||
#include "ps2_mouse.h"
|
||||
#include "usb_mouse.h"
|
||||
|
||||
// #define ISR_PRINTF
|
||||
|
||||
EXT_RAM_BSS_ATTR tinfl_decompressor decomp_buf;
|
||||
tinfl_decompressor *decomp = &decomp_buf;
|
||||
|
||||
const char TAG[] = "main";
|
||||
int usb_mouse_start_on_boot = 0;
|
||||
|
||||
u32 task_ram_type_critical = MALLOC_CAP_INTERNAL;
|
||||
u32 task_ram_type_non_critical = MALLOC_CAP_SPIRAM;
|
||||
|
||||
// ------------- Debug helpers
|
||||
|
||||
size_t sram_used_prev;
|
||||
|
||||
void log_sram_used(const char *name)
|
||||
{
|
||||
size_t total = heap_caps_get_total_size(MALLOC_CAP_INTERNAL);
|
||||
size_t free = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
size_t used = total - free;
|
||||
int delta = (int)used - (int)sram_used_prev;
|
||||
|
||||
ESP_LOGI("SRAM", "%s, SRAM used: %d bytes", name, delta);
|
||||
sram_used_prev = used;
|
||||
}
|
||||
|
||||
#ifdef ISR_PRINTF
|
||||
|
||||
#define PRINT_QUEUE_LENGTH 100
|
||||
static QueueHandle_t printQueue;
|
||||
|
||||
void print_task(void *pvParameters)
|
||||
{
|
||||
char *message;
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (xQueueReceive(printQueue, &message, portMAX_DELAY))
|
||||
printf("%s", message);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C"
|
||||
{
|
||||
void print_isr(const char* msg)
|
||||
{
|
||||
xQueueSendFromISR(printQueue, &msg, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ISR_PRINTF
|
||||
|
||||
// void IRAM_ATTR splash(int n)
|
||||
// {
|
||||
// while (n--)
|
||||
// {
|
||||
// gpio_set_level((gpio_num_t)GPIO_TEST1, 1); gpio_set_level((gpio_num_t)GPIO_TEST1, 0); // !!!
|
||||
// }
|
||||
// }
|
||||
|
||||
// ------------- System start and initialization
|
||||
|
||||
extern "C" void app_main()
|
||||
{
|
||||
esp_err_t err;
|
||||
|
||||
// ----- Test GPIO init
|
||||
// gpio_set_direction((gpio_num_t)GPIO_TEST1, GPIO_MODE_OUTPUT);
|
||||
// gpio_set_direction((gpio_num_t)GPIO_TEST2, GPIO_MODE_OUTPUT);
|
||||
// gpio_set_direction((gpio_num_t)GPIO_TEST3, GPIO_MODE_OUTPUT);
|
||||
sram_used_prev = heap_caps_get_total_size(MALLOC_CAP_INTERNAL) - heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
|
||||
// ----- Runtime inits
|
||||
stats::init();
|
||||
bootloader_random_enable();
|
||||
|
||||
#ifdef ISR_PRINTF
|
||||
// ----- ISR printf init
|
||||
printQueue = xQueueCreate(PRINT_QUEUE_LENGTH, sizeof(char*));
|
||||
log_sram_used("QueueCreate printQueue");
|
||||
xTaskCreatePinnedToCoreWithCaps(print_task, "isr-printf", 2048, NULL, 1, NULL, 0, task_ram_type_non_critical);
|
||||
log_sram_used("TaskCreate print_task");
|
||||
#endif
|
||||
|
||||
|
||||
// ----- NVS + params init
|
||||
initialize_nvs();
|
||||
ESP_ERROR_CHECK(app_params_load());
|
||||
|
||||
usb_mouse_start_on_boot = app_params.usb_mode ? 1 : 0;
|
||||
|
||||
ESP_LOGI("MAIN", "usb_mode=%u, usb_mouse_start_on_boot=%d", app_params.usb_mode, usb_mouse_start_on_boot);
|
||||
|
||||
// ----- Wi-Fi init
|
||||
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
|
||||
net.is_init = false;
|
||||
net.state = NETWORK_CLOSED;
|
||||
|
||||
if (wifi_is_enabled())
|
||||
{
|
||||
initialize_wifi();
|
||||
wifi_start_autoconnect();
|
||||
net.is_init = true;
|
||||
log_sram_used("initialize_wifi");
|
||||
}
|
||||
else
|
||||
ESP_LOGI("MAIN", "WiFi disabled by config");
|
||||
#endif
|
||||
|
||||
// ----- Helper init
|
||||
helper_queue = xQueueCreateWithCaps(2, sizeof(int), task_ram_type_non_critical);
|
||||
log_sram_used("QueueCreate helper_queue");
|
||||
xTaskCreatePinnedToCoreWithCaps(helper_task, "helper", 6144, NULL, HELPER_TASK_PRIO, NULL, 0, task_ram_type_critical);
|
||||
log_sram_used("TaskCreate helper_task");
|
||||
|
||||
// ----- LibXM init
|
||||
initialize_xm();
|
||||
|
||||
// ----- Network client init
|
||||
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
|
||||
http_init();
|
||||
log_sram_used("http_init");
|
||||
|
||||
gopher_init();
|
||||
log_sram_used("gopher_init");
|
||||
|
||||
stream_init();
|
||||
log_sram_used("stream_init");
|
||||
#endif
|
||||
|
||||
// ----- SDMMC init
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32P4)
|
||||
sd_ldo_init();
|
||||
#endif
|
||||
|
||||
// ----- SPI slave init
|
||||
init_slave_hd();
|
||||
|
||||
// ----- FT812 init
|
||||
init_ft8xx();
|
||||
log_sram_used("init_ft8xx");
|
||||
|
||||
// ----- PS/2 mouse init
|
||||
err = ps2_mouse_start();
|
||||
if (err != ESP_OK)
|
||||
ESP_LOGE("PS2", "ps2_mouse_start failed: %s", esp_err_to_name(err));
|
||||
log_sram_used("PS/2 mouse init");
|
||||
|
||||
// ----- USB mouse init
|
||||
if (usb_mouse_start_on_boot)
|
||||
{
|
||||
err = usb_mouse_start();
|
||||
if (err != ESP_OK)
|
||||
ESP_LOGE("USBM", "usb_mouse_start failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
log_sram_used("USB mouse init");
|
||||
|
||||
// ----- Console init
|
||||
initialize_console();
|
||||
xTaskCreatePinnedToCoreWithCaps(console_task, "console", 6144, NULL, CONSOLE_TASK_PRIO, NULL, 0, task_ram_type_critical);
|
||||
log_sram_used("TaskCreate console_task");
|
||||
|
||||
// ----- AT command UART init
|
||||
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
|
||||
at_cip_init();
|
||||
at_uart_init();
|
||||
// Stack sizes sized for AT+CIPSTART="SSL": esp_tls_conn_new_sync()'s DNS lookup
|
||||
// (getaddrinfo -> lwip_getaddrinfo -> netconn_gethostbyname_addrtype_n -> ...)
|
||||
// is a much deeper call chain than the plain-TCP path's gethostbyname(), and
|
||||
// mbedtls's own handshake/record processing adds further stack depth on top -
|
||||
// the original 4096/3072 sizes (fine for plain TCP/UDP) overflowed and crashed
|
||||
// with a seemingly unrelated LoadProhibited panic inside a spinlock deep in
|
||||
// that chain, confirmed via backtrace against esp-spi.elf.
|
||||
xTaskCreatePinnedToCoreWithCaps(at_task, "at-cmd", 12288, NULL, AT_TASK_PRIO, &g_at_task_handle, 1, task_ram_type_critical);
|
||||
xTaskCreatePinnedToCoreWithCaps(at_cip_rx_task, "at-cip-rx", 8192, NULL, AT_TASK_PRIO, &g_at_cip_rx_task_handle, 1, task_ram_type_critical);
|
||||
log_sram_used("TaskCreate at_task");
|
||||
#endif
|
||||
|
||||
// ----- SMB2/3 network share (Phase 1 fake filesystem + Phase 2 SPI bridge to the Z80, see project plan)
|
||||
// The task is created here unconditionally (cheap - it just registers
|
||||
// console commands/queues and then blocks), but it does NOT start
|
||||
// listening on its own anymore: it sits idle until ESP_CMD_VFS_SERVER_START
|
||||
// (see vfs_bridge_start_server()/TASK_VFS_SERVER_START in helper.cpp) -
|
||||
// the smbvfs.wmf plugin explicitly starts/stops the server (and the other
|
||||
// services it pauses meanwhile) instead of it riding along with WiFi.
|
||||
#if defined(CONFIG_ESP_WIFI_ENABLED) && CONFIG_ESP_WIFI_ENABLED
|
||||
vfs_request_init();
|
||||
vfs_bridge_init();
|
||||
xTaskCreatePinnedToCoreWithCaps(smb_server_task, "smb-server", 8192, NULL, SMB_TASK_PRIO, NULL, 1, task_ram_type_critical);
|
||||
log_sram_used("TaskCreate smb_server_task");
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "miniz.h"
|
||||
#include "tracker.h"
|
||||
|
||||
typedef int8_t i8;
|
||||
typedef int16_t i16;
|
||||
typedef int32_t i32;
|
||||
typedef int64_t i64;
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
extern u32 task_ram_type_critical;
|
||||
extern u32 task_ram_type_non_critical;
|
||||
|
||||
enum
|
||||
{
|
||||
// Core 0
|
||||
CONSOLE_TASK_PRIO = 2,
|
||||
WIFI_AUTOCONNECT_TASK_PRIO = 3,
|
||||
STREAMER_TASK_PRIO = 4,
|
||||
TCPIP_TASK_PRIO = 5,
|
||||
WIFI_TASK_PRIO = 6,
|
||||
SPI_BG_TASK_PRIO = 7,
|
||||
TRACKER_HELPER_TASK_PRIO = 8,
|
||||
HELPER_TASK_PRIO = 9,
|
||||
USB_MOUSE_TASK_PRIO = 10,
|
||||
USB_MOUSE_LIB_TASK_PRIO = 10,
|
||||
USB_MOUSE_HID_TASK_PRIO = 11,
|
||||
SLAVE_TASK_PRIO = 14,
|
||||
|
||||
// Core 1
|
||||
TRACKER_PLAYER_TASK_PRIO = 10,
|
||||
I2S_TASK_PRIO = 12,
|
||||
AT_TASK_PRIO = 4,
|
||||
SMB_TASK_PRIO = 4,
|
||||
};
|
||||
|
||||
#define max(x, y) (((x) > (y)) ? (x) : (y))
|
||||
#define min(x, y) (((x) < (y)) ? (x) : (y))
|
||||
|
||||
extern tinfl_decompressor *decomp;
|
||||
|
||||
// #define VERBOSE
|
||||
|
||||
#define _delay_ms(a) vTaskDelay(pdMS_TO_TICKS(a));
|
||||
|
||||
#ifdef CONFIG_SPIRAM
|
||||
#define malloc_spiram(a) heap_caps_malloc(a, MALLOC_CAP_SPIRAM)
|
||||
#else
|
||||
#define malloc_spiram(a) malloc(a)
|
||||
#endif
|
||||
|
||||
void log_sram_used(const char *name);
|
||||
@@ -0,0 +1,231 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "esp_spi_defs.h"
|
||||
#include "spi_slave.h"
|
||||
#include "helper.h"
|
||||
#include "mem_obj.h"
|
||||
#include "tracker.h"
|
||||
#include "sfx.h"
|
||||
|
||||
const char TAG[] = "mem_obj";
|
||||
|
||||
EXT_RAM_BSS_ATTR MEM_OBJ mem_obj[OBJ_HANDLES_MAX] = {};
|
||||
|
||||
int find_avail_handle()
|
||||
{
|
||||
for (int i = 0; i < OBJ_HANDLES_MAX; i++)
|
||||
if (!mem_obj[i].type)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int check_handle(int h)
|
||||
{
|
||||
return mem_obj[h].type != 0;
|
||||
}
|
||||
|
||||
int mem_obj_can_flat_rw(int h)
|
||||
{
|
||||
if (!check_handle(h)) return 0;
|
||||
|
||||
switch (mem_obj[h].type)
|
||||
{
|
||||
case OBJ_TYPE_LIB:
|
||||
case OBJ_TYPE_XMC:
|
||||
case OBJ_TYPE_MDC:
|
||||
case OBJ_TYPE_S3C:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int delete_obj(int h)
|
||||
{
|
||||
switch (mem_obj[h].type)
|
||||
{
|
||||
case OBJ_TYPE_LIB:
|
||||
{
|
||||
if (mem_obj[h].text) free(mem_obj[h].text);
|
||||
if (mem_obj[h].data) free(mem_obj[h].data);
|
||||
if (mem_obj[h].rodata) free(mem_obj[h].rodata);
|
||||
if (mem_obj[h].bss) free(mem_obj[h].bss);
|
||||
memset(&mem_obj[h], 0, sizeof(MEM_OBJ));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
case OBJ_TYPE_XMC:
|
||||
case OBJ_TYPE_MDC:
|
||||
case OBJ_TYPE_S3C:
|
||||
if (mem_obj[h].state == TRACK_OBJ_ST_PLAYING)
|
||||
{
|
||||
ESP_LOGE(TAG, "Cannot delete playing tracker object: handle %02X", h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (mem_obj[h].addr)
|
||||
{
|
||||
xm_free_context((xm_context_t*)mem_obj[h].addr);
|
||||
memset(&mem_obj[h], 0, sizeof(MEM_OBJ));
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case OBJ_TYPE_WAV:
|
||||
{
|
||||
u8 old_state = mem_obj[h].state;
|
||||
mem_obj[h].state = OBJ_ST_DELETING;
|
||||
|
||||
esp_err_t err = sfx_stop_handle_sync(h);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
mem_obj[h].state = old_state;
|
||||
ESP_LOGE(TAG, "Cannot stop WAV object users before delete: handle %02X, err %s", h, esp_err_to_name(err));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (mem_obj[h].addr) free(mem_obj[h].addr);
|
||||
memset(&mem_obj[h], 0, sizeof(MEM_OBJ));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void write_obj(int handle, const void *from, int size)
|
||||
{
|
||||
memcpy(mem_obj[handle].addr, from, max(mem_obj[handle].size, size));
|
||||
}
|
||||
|
||||
int attach_obj(void *addr, int obj_size, int obj_type)
|
||||
{
|
||||
if (!addr || obj_size <= 0)
|
||||
{
|
||||
set_status(ESP_ERR_INV_PARAM);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int handle = find_avail_handle();
|
||||
if (handle == -1)
|
||||
{
|
||||
set_status(ESP_ERR_OUT_OF_HANDLES);
|
||||
ESP_LOGE(TAG, "Cannot allocate handle for an object!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (obj_type)
|
||||
{
|
||||
case OBJ_TYPE_XM:
|
||||
case OBJ_TYPE_XMC:
|
||||
case OBJ_TYPE_MOD:
|
||||
case OBJ_TYPE_MDC:
|
||||
case OBJ_TYPE_S3M:
|
||||
case OBJ_TYPE_S3C:
|
||||
case OBJ_TYPE_WAV:
|
||||
case OBJ_TYPE_DATA:
|
||||
case OBJ_TYPE_ELF:
|
||||
case OBJ_TYPE_HST:
|
||||
case OBJ_TYPE_ZIP:
|
||||
case OBJ_TYPE_DATAF:
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGE(TAG, "Cannot attach unknown type object!");
|
||||
set_status(ESP_ERR_INV_OBJ_TYPE);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (obj_type == OBJ_TYPE_XMC || obj_type == OBJ_TYPE_MDC || obj_type == OBJ_TYPE_S3C)
|
||||
{
|
||||
xm_context_t *ctx = (xm_context_t*)addr;
|
||||
|
||||
if (!tracker_context_has_registered_segments(ctx))
|
||||
{
|
||||
size_t context_size = ctx->ctx_size ? ctx->ctx_size : (size_t)obj_size;
|
||||
if (!ctx->ctx_size) ctx->ctx_size = context_size;
|
||||
|
||||
if (!tracker_context_register_segment(ctx, addr, context_size, TRACKER_CONTEXT_SEG_CONTEXT))
|
||||
{
|
||||
set_status(ESP_ERR_INV_STATE);
|
||||
ESP_LOGE(TAG, "Cannot attach tracker object without segment ownership!");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mem_obj[handle].addr = addr;
|
||||
mem_obj[handle].size = obj_size;
|
||||
mem_obj[handle].type = obj_type;
|
||||
mem_obj[handle].state = OBJ_ST_NONE;
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
int make_obj(int obj_size, int obj_type)
|
||||
{
|
||||
void *obj_addr = NULL;
|
||||
|
||||
int handle = find_avail_handle();
|
||||
|
||||
if (handle == -1)
|
||||
{
|
||||
set_status(ESP_ERR_OUT_OF_HANDLES);
|
||||
ESP_LOGE(TAG, "Cannot allocate handle for an object!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (obj_type)
|
||||
{
|
||||
case OBJ_TYPE_XM:
|
||||
case OBJ_TYPE_MOD:
|
||||
case OBJ_TYPE_S3M:
|
||||
case OBJ_TYPE_WAV:
|
||||
case OBJ_TYPE_DATA:
|
||||
case OBJ_TYPE_ELF:
|
||||
case OBJ_TYPE_HST:
|
||||
case OBJ_TYPE_ZIP:
|
||||
obj_addr = malloc_spiram(obj_size);
|
||||
break;
|
||||
|
||||
case OBJ_TYPE_DATAF:
|
||||
obj_addr = heap_caps_malloc(obj_size, MALLOC_CAP_INTERNAL);
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
ESP_LOGE(TAG, "Cannot create unknown type object!");
|
||||
set_status(ESP_ERR_INV_OBJ_TYPE);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!obj_addr)
|
||||
{
|
||||
set_status(ESP_ERR_OUT_OF_MEMORY);
|
||||
ESP_LOGE(TAG, "Cannot allocate memory for an object! (%d bytes)", obj_size);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef VERBOSE
|
||||
else
|
||||
printf("Object created: type %02X, handle %02X, addr %08X, size %u\r\n", obj_type, handle, (unsigned int)obj_addr, obj_size);
|
||||
#endif
|
||||
|
||||
mem_obj[handle].addr = obj_addr;
|
||||
mem_obj[handle].size = obj_size;
|
||||
mem_obj[handle].type = obj_type;
|
||||
mem_obj[handle].state = OBJ_ST_NONE;
|
||||
|
||||
return handle;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#pragma pack(1)
|
||||
typedef struct
|
||||
{
|
||||
// Common
|
||||
void *addr;
|
||||
size_t size;
|
||||
u8 type;
|
||||
u8 state;
|
||||
|
||||
// Lib
|
||||
void *text;
|
||||
void *rodata;
|
||||
void *data;
|
||||
void *bss;
|
||||
|
||||
u32 sz_text;
|
||||
u32 sz_rodata;
|
||||
u32 sz_data;
|
||||
u32 sz_bss;
|
||||
|
||||
void *entry;
|
||||
} MEM_OBJ;
|
||||
#pragma pack()
|
||||
|
||||
#define OBJ_HANDLES_MAX 256
|
||||
extern MEM_OBJ mem_obj[OBJ_HANDLES_MAX];
|
||||
|
||||
int find_avail_handle();
|
||||
int check_handle(int h);
|
||||
int delete_obj(int h);
|
||||
int make_obj(int obj_size, int obj_type);
|
||||
int attach_obj(void *addr, int obj_size, int obj_type);
|
||||
int mem_obj_can_flat_rw(int h);
|
||||
void write_obj(int handle, const void *from, int size);
|
||||
@@ -0,0 +1,466 @@
|
||||
// See named_pipe_rpc.h. Minimal MS-RPCE (bind/bind_ack/request/response/
|
||||
// fault) plus just enough MS-WKST NetrWkstaGetInfo and MS-SRVS
|
||||
// NetrShareGetInfo/NetrServerGetInfo to answer what Windows Explorer asks
|
||||
// before it will browse a share. Field layouts follow the standard NDR
|
||||
// wire rules (conformant/varying strings, deferred pointer data,
|
||||
// embedded-union encoding). A packet capture confirmed Explorer really
|
||||
// does require a working reply here (clean STATUS_NOT_IMPLEMENTED
|
||||
// rejections just get retried forever) and confirmed which two pipes/
|
||||
// opnums are being probed - but this specific bind/response encoding is
|
||||
// new and not yet verified against a real exchange; the opnum logging
|
||||
// below is there to check that on the next capture.
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "named_pipe_rpc.h"
|
||||
|
||||
static const char *TAG = "rpc_pipe";
|
||||
|
||||
// ---- DCE/RPC wire constants -----------------------------------------
|
||||
|
||||
#define RPC_PTYPE_REQUEST 0
|
||||
#define RPC_PTYPE_RESPONSE 2
|
||||
#define RPC_PTYPE_FAULT 3
|
||||
#define RPC_PTYPE_BIND 11
|
||||
#define RPC_PTYPE_BIND_ACK 12
|
||||
#define RPC_PTYPE_BIND_NAK 13
|
||||
|
||||
#define PFC_FIRST_FRAG 0x01
|
||||
#define PFC_LAST_FRAG 0x02
|
||||
|
||||
// UUID 8a885d04-1ceb-11c9-9fe8-08002b104860 v2.0 - the classic NDR transfer
|
||||
// syntax essentially every DCE/RPC client/server supports.
|
||||
static const uint8_t NDR_TRANSFER_SYNTAX_UUID[16] = {
|
||||
0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11,
|
||||
0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60
|
||||
};
|
||||
#define NDR_TRANSFER_SYNTAX_VERSION 2
|
||||
|
||||
// ---- tiny append-only byte buffer for building replies ---------------
|
||||
|
||||
#define RPC_BUF_MAX 512
|
||||
|
||||
struct rpc_buf
|
||||
{
|
||||
uint8_t data[RPC_BUF_MAX];
|
||||
uint32_t len;
|
||||
};
|
||||
|
||||
static void buf_reset(rpc_buf *b) { b->len = 0; }
|
||||
|
||||
static void buf_put(rpc_buf *b, const void *src, uint32_t n)
|
||||
{
|
||||
if (b->len + n > RPC_BUF_MAX) n = RPC_BUF_MAX - b->len;
|
||||
memcpy(b->data + b->len, src, n);
|
||||
b->len += n;
|
||||
}
|
||||
|
||||
static void buf_u8(rpc_buf *b, uint8_t v) { buf_put(b, &v, 1); }
|
||||
static void buf_u16(rpc_buf *b, uint16_t v) { buf_put(b, &v, 2); }
|
||||
static void buf_u32(rpc_buf *b, uint32_t v) { buf_put(b, &v, 4); }
|
||||
|
||||
static void buf_align4(rpc_buf *b)
|
||||
{
|
||||
while (b->len % 4) buf_u8(b, 0);
|
||||
}
|
||||
|
||||
// NDR conformant+varying WCHAR string: max_count, offset(=0), actual_count
|
||||
// (all in WCHARs, including the NUL terminator), then the UTF-16LE bytes,
|
||||
// padded to a 4-byte boundary.
|
||||
static void buf_ndr_string(rpc_buf *b, const char *s)
|
||||
{
|
||||
uint32_t n = (uint32_t)strlen(s) + 1; // include NUL
|
||||
buf_u32(b, n); // max_count
|
||||
buf_u32(b, 0); // offset
|
||||
buf_u32(b, n); // actual_count
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
{
|
||||
buf_u16(b, (uint8_t)s[i]); // ASCII-safe names only, widened to UTF-16LE
|
||||
}
|
||||
buf_align4(b);
|
||||
}
|
||||
|
||||
// ---- per-handle pipe state --------------------------------------------
|
||||
|
||||
#define MAX_RPC_PIPES 4
|
||||
|
||||
struct rpc_pipe_state
|
||||
{
|
||||
bool in_use;
|
||||
smb2_file_id file_id;
|
||||
rpc_pipe_type type;
|
||||
uint32_t call_id;
|
||||
rpc_buf reply;
|
||||
bool reply_pending;
|
||||
struct smb2_context *owner;
|
||||
};
|
||||
|
||||
static rpc_pipe_state s_pipes[MAX_RPC_PIPES];
|
||||
|
||||
static rpc_pipe_state *find_pipe(const smb2_file_id file_id)
|
||||
{
|
||||
for (int i = 0; i < MAX_RPC_PIPES; i++)
|
||||
if (s_pipes[i].in_use && !memcmp(s_pipes[i].file_id, file_id, sizeof(smb2_file_id)))
|
||||
return &s_pipes[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rpc_pipe_type rpc_pipe_type_for_name(const char *name)
|
||||
{
|
||||
if (!name) return RPC_PIPE_NONE;
|
||||
if (!strcmp(name, "wkssvc")) return RPC_PIPE_WKSSVC;
|
||||
if (!strcmp(name, "srvsvc")) return RPC_PIPE_SRVSVC;
|
||||
return RPC_PIPE_NONE;
|
||||
}
|
||||
|
||||
void rpc_pipe_open(const smb2_file_id file_id, rpc_pipe_type type, struct smb2_context *owner)
|
||||
{
|
||||
rpc_pipe_state *slot = find_pipe(file_id);
|
||||
if (!slot)
|
||||
{
|
||||
for (int i = 0; i < MAX_RPC_PIPES; i++)
|
||||
if (!s_pipes[i].in_use) { slot = &s_pipes[i]; break; }
|
||||
}
|
||||
if (!slot)
|
||||
{
|
||||
ESP_LOGW(TAG, "rpc_pipe_open: no free slot");
|
||||
return;
|
||||
}
|
||||
memset(slot, 0, sizeof(*slot));
|
||||
slot->in_use = true;
|
||||
memcpy(slot->file_id, file_id, sizeof(smb2_file_id));
|
||||
slot->type = type;
|
||||
slot->owner = owner;
|
||||
}
|
||||
|
||||
void rpc_pipe_close(const smb2_file_id file_id)
|
||||
{
|
||||
rpc_pipe_state *slot = find_pipe(file_id);
|
||||
if (slot) slot->in_use = false;
|
||||
}
|
||||
|
||||
bool rpc_pipe_is_open(const smb2_file_id file_id)
|
||||
{
|
||||
return find_pipe(file_id) != NULL;
|
||||
}
|
||||
|
||||
void rpc_pipe_reset_for_owner(struct smb2_context *owner)
|
||||
{
|
||||
for (int i = 0; i < MAX_RPC_PIPES; i++)
|
||||
if (s_pipes[i].in_use && s_pipes[i].owner == owner)
|
||||
s_pipes[i].in_use = false;
|
||||
}
|
||||
|
||||
// ---- DCE/RPC common header helpers ------------------------------------
|
||||
|
||||
static void put_rpc_header(rpc_buf *b, uint8_t ptype, uint32_t call_id)
|
||||
{
|
||||
buf_u8(b, 5); // rpc_vers
|
||||
buf_u8(b, 0); // rpc_vers_minor
|
||||
buf_u8(b, ptype);
|
||||
buf_u8(b, PFC_FIRST_FRAG | PFC_LAST_FRAG);
|
||||
buf_u8(b, 0x10); buf_u8(b, 0x00); buf_u8(b, 0x00); buf_u8(b, 0x00); // drep: little-endian/ASCII/IEEE
|
||||
buf_u16(b, 0); // frag_length - patched in fixup_frag_length()
|
||||
buf_u16(b, 0); // auth_length
|
||||
buf_u32(b, call_id);
|
||||
}
|
||||
|
||||
static void fixup_frag_length(rpc_buf *b)
|
||||
{
|
||||
uint16_t len = (uint16_t)b->len;
|
||||
memcpy(b->data + 8, &len, 2); // frag_length is at header offset 8
|
||||
}
|
||||
|
||||
// ---- BIND handling ------------------------------------------------------
|
||||
|
||||
// Parses one client bind PDU (after the 16-byte common header) and builds
|
||||
// a bind_ack accepting the first context whose offered transfer syntax
|
||||
// matches classic NDR, rejecting (per-context) any others - modern Windows
|
||||
// clients routinely offer NDR64 and a "bind time feature negotiation"
|
||||
// pseudo-context alongside classic NDR in the same bind.
|
||||
static void handle_bind(rpc_pipe_state *pipe, const uint8_t *body, uint32_t body_len)
|
||||
{
|
||||
rpc_buf *out = &pipe->reply;
|
||||
buf_reset(out);
|
||||
put_rpc_header(out, RPC_PTYPE_BIND_ACK, pipe->call_id);
|
||||
|
||||
if (body_len < 12) { fixup_frag_length(out); pipe->reply_pending = true; return; }
|
||||
|
||||
uint16_t max_xmit; memcpy(&max_xmit, body + 0, 2);
|
||||
uint16_t max_recv; memcpy(&max_recv, body + 2, 2);
|
||||
uint8_t n_context = body[8];
|
||||
|
||||
buf_u16(out, max_xmit);
|
||||
buf_u16(out, max_recv);
|
||||
buf_u32(out, 0x12345678); // assoc_group_id - arbitrary nonzero
|
||||
|
||||
// Secondary address string: a real Windows server sends "\PIPE\<name>\0"
|
||||
// here (confirmed via packet capture); we sent an empty string before.
|
||||
const char *sec_addr = (pipe->type == RPC_PIPE_SRVSVC) ? "\\PIPE\\srvsvc" : "\\PIPE\\wkssvc";
|
||||
uint16_t sec_addr_len = (uint16_t)strlen(sec_addr) + 1; // include NUL
|
||||
buf_u16(out, sec_addr_len);
|
||||
buf_put(out, sec_addr, sec_addr_len);
|
||||
buf_align4(out); // realign before results array
|
||||
if (n_context > 32) n_context = 0; // sanity guard
|
||||
buf_u8(out, n_context);
|
||||
buf_u8(out, 0); buf_u8(out, 0); buf_u8(out, 0); // pad
|
||||
|
||||
uint32_t off = 12;
|
||||
for (uint8_t c = 0; c < n_context; c++)
|
||||
{
|
||||
if (off + 4 > body_len) break;
|
||||
uint8_t n_transfer = body[off + 2];
|
||||
// p_cont_id(2) + n_transfer_syn(1) + reserved(1) + abstract uuid(16) + abstract version(4)
|
||||
uint32_t ts_base = off + 4 + 16 + 4;
|
||||
|
||||
bool accepted = false;
|
||||
for (uint8_t t = 0; t < n_transfer; t++)
|
||||
{
|
||||
uint32_t ts_off = ts_base + (uint32_t)t * 20;
|
||||
if (ts_off + 16 > body_len) break;
|
||||
if (!memcmp(body + ts_off, NDR_TRANSFER_SYNTAX_UUID, 16))
|
||||
{
|
||||
accepted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (accepted)
|
||||
{
|
||||
buf_u16(out, 0); // ack_result: acceptance
|
||||
buf_u16(out, 0); // ack_reason
|
||||
buf_put(out, NDR_TRANSFER_SYNTAX_UUID, 16);
|
||||
buf_u32(out, NDR_TRANSFER_SYNTAX_VERSION);
|
||||
}
|
||||
else
|
||||
{
|
||||
buf_u16(out, 2); // ack_result: provider_rejection
|
||||
buf_u16(out, 2); // ack_reason: transfer syntaxes not supported
|
||||
uint8_t zero_uuid[16] = {0};
|
||||
buf_put(out, zero_uuid, 16);
|
||||
buf_u32(out, 0);
|
||||
}
|
||||
|
||||
// Advance past every transfer syntax offered for this context (not
|
||||
// just however many we scanned before matching/giving up), so the
|
||||
// next context element is found at the right offset.
|
||||
off = ts_base + (uint32_t)n_transfer * 20;
|
||||
}
|
||||
|
||||
fixup_frag_length(out);
|
||||
pipe->reply_pending = true;
|
||||
}
|
||||
|
||||
// ---- WKSSVC: NetrWkstaGetInfo (opnum 0) --------------------------------
|
||||
|
||||
static void wkssvc_dispatch(rpc_pipe_state *pipe, uint16_t opnum, uint16_t context_id,
|
||||
const uint8_t *stub, uint32_t stub_len)
|
||||
{
|
||||
rpc_buf *out = &pipe->reply;
|
||||
buf_reset(out);
|
||||
|
||||
if (opnum != 0)
|
||||
{
|
||||
put_rpc_header(out, RPC_PTYPE_FAULT, pipe->call_id);
|
||||
buf_u32(out, 0); buf_u16(out, context_id); buf_u8(out, 0); buf_u8(out, 0);
|
||||
buf_u32(out, 0x1c010002); // nca_s_op_rng_error
|
||||
fixup_frag_length(out);
|
||||
pipe->reply_pending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
put_rpc_header(out, RPC_PTYPE_RESPONSE, pipe->call_id);
|
||||
buf_u32(out, 0); // alloc_hint - patched below
|
||||
buf_u16(out, context_id);
|
||||
buf_u8(out, 0); buf_u8(out, 0); // cancel_count, reserved
|
||||
|
||||
uint32_t stub_start = out->len;
|
||||
|
||||
// WkstaInfo is a top-level [out, switch_is(Level)] pointer parameter - a
|
||||
// ref pointer by default, which NDR does NOT prefix with a referent ID
|
||||
// (only unique/embedded pointers get one). A stray extra referent here
|
||||
// shifted every field after it by 4 bytes for any strict NDR client -
|
||||
// confirmed via Wireshark's own MS-SRVS/MS-WKST dissector misreading our
|
||||
// "referent" value as the Level field ("Level: 262144" = 0x00040000, our
|
||||
// old srvsvc referent) and flagging the rest of the packet malformed.
|
||||
// Real Windows Explorer's RPC runtime is equally strict; only Samba's
|
||||
// permissive smbclient tolerated this, which is why Day-1 testing looked
|
||||
// fine. The union's own switch value is the first real stub byte.
|
||||
buf_u32(out, 100); // encapsulated union switch: level 100
|
||||
buf_u32(out, 500); // wki100_platform_id = PLATFORM_ID_NT
|
||||
buf_u32(out, 0x00020004); // referent: computer_name
|
||||
buf_u32(out, 0x00020008); // referent: langroup
|
||||
buf_u32(out, 10); // wki100_ver_major
|
||||
buf_u32(out, 0); // wki100_ver_minor
|
||||
buf_ndr_string(out, "ZXEVO"); // deferred: computer_name
|
||||
buf_ndr_string(out, "WORKGROUP"); // deferred: langroup
|
||||
buf_u32(out, 0); // NET_API_STATUS return: NERR_Success
|
||||
|
||||
uint32_t stub_len_out = out->len - stub_start;
|
||||
memcpy(out->data + 16, &stub_len_out, 4); // alloc_hint field, right after the 16-byte common header
|
||||
fixup_frag_length(out);
|
||||
pipe->reply_pending = true;
|
||||
(void)stub; (void)stub_len;
|
||||
}
|
||||
|
||||
// ---- SRVSVC: NetrShareGetInfo (16) / NetrServerGetInfo (21) ------------
|
||||
|
||||
static void srvsvc_dispatch(rpc_pipe_state *pipe, uint16_t opnum, uint16_t context_id,
|
||||
const uint8_t *stub, uint32_t stub_len)
|
||||
{
|
||||
rpc_buf *out = &pipe->reply;
|
||||
buf_reset(out);
|
||||
|
||||
if (opnum != 15 && opnum != 16 && opnum != 21)
|
||||
{
|
||||
put_rpc_header(out, RPC_PTYPE_FAULT, pipe->call_id);
|
||||
buf_u32(out, 0); buf_u16(out, context_id); buf_u8(out, 0); buf_u8(out, 0);
|
||||
buf_u32(out, 0x1c010002); // nca_s_op_rng_error
|
||||
fixup_frag_length(out);
|
||||
pipe->reply_pending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
put_rpc_header(out, RPC_PTYPE_RESPONSE, pipe->call_id);
|
||||
buf_u32(out, 0); // alloc_hint placeholder
|
||||
buf_u16(out, context_id);
|
||||
buf_u8(out, 0); buf_u8(out, 0);
|
||||
|
||||
uint32_t stub_start = out->len;
|
||||
|
||||
if (opnum == 15)
|
||||
{
|
||||
// NetrShareEnum -> [in,out] LPSHARE_ENUM_STRUCT InfoStruct, level 1,
|
||||
// one entry. Explorer cross-checks the share it navigated to actually
|
||||
// appears in this list before it will browse it - this used to say
|
||||
// "test" (a leftover from Phase 1's own fake test share, never updated
|
||||
// once tree_connect_handler started accepting real share names) while
|
||||
// the real share is named "share" (see smbvfs's own share_name[] and
|
||||
// what clients actually tree_connect to, \\<esp32-ip>\share) - the
|
||||
// mismatch was confirmed on real hardware to make Explorer repeatedly
|
||||
// re-open and re-query srvsvc many times (never trusting the mismatch
|
||||
// enough to actually browse the share) before eventually giving up
|
||||
// entirely, while a real TREE_CONNECT to "share" itself kept
|
||||
// succeeding cleanly the whole time.
|
||||
// InfoStruct is a top-level [in,out] ref pointer parameter - no
|
||||
// referent ID at this level (see wkssvc_dispatch's comment above);
|
||||
// Level is the first real stub byte. Everything below this - the
|
||||
// union's embedded Ctr1 pointer, the Buffer array pointer, the
|
||||
// per-entry name/remark pointers - are genuinely embedded/unique
|
||||
// pointers and correctly keep their referent IDs.
|
||||
buf_u32(out, 1); // SHARE_ENUM_STRUCT.Level
|
||||
buf_u32(out, 1); // encapsulated union switch (repeats Level)
|
||||
buf_u32(out, 0x00040004); // referent: SHARE_INFO_1_CONTAINER*
|
||||
buf_u32(out, 1); // container.EntriesRead
|
||||
buf_u32(out, 0x00040008); // referent: Buffer (array of SHARE_INFO_1)
|
||||
buf_u32(out, 1); // conformant array max_count
|
||||
buf_u32(out, 0x0004000c); // entry[0] referent: shi1_netname
|
||||
buf_u32(out, 0); // entry[0] shi1_type: STYPE_DISKTREE
|
||||
buf_u32(out, 0x00040010); // entry[0] referent: shi1_remark
|
||||
buf_ndr_string(out, "share");
|
||||
buf_ndr_string(out, "ZX Evolution share");
|
||||
buf_u32(out, 1); // [out] TotalEntries
|
||||
buf_u32(out, 0); // [in,out, unique] ResumeHandle: NULL (done)
|
||||
}
|
||||
else if (opnum == 16)
|
||||
{
|
||||
// NetrShareGetInfo -> LPSHARE_INFO union, level 1 (SHARE_INFO_1).
|
||||
// Top-level [out, switch_is(Level)] ref pointer - no referent ID here
|
||||
// either (see wkssvc_dispatch's comment); the union's switch value is
|
||||
// the first real stub byte.
|
||||
buf_u32(out, 1); // switch: level 1
|
||||
buf_u32(out, 0x00030004); // referent: shi1_netname
|
||||
buf_u32(out, 0); // shi1_type: STYPE_DISKTREE
|
||||
buf_u32(out, 0x00030008); // referent: shi1_remark
|
||||
buf_ndr_string(out, "share");
|
||||
buf_ndr_string(out, "ZX Evolution share");
|
||||
}
|
||||
else
|
||||
{
|
||||
// NetrServerGetInfo -> LPSERVER_INFO union, level 100 (SERVER_INFO_100).
|
||||
// Same top-level-ref-pointer-has-no-referent rule as above.
|
||||
buf_u32(out, 100); // switch: level 100
|
||||
buf_u32(out, 500); // sv100_platform_id = PLATFORM_ID_NT
|
||||
buf_u32(out, 0x00030014); // referent: sv100_name
|
||||
buf_ndr_string(out, "ZXEVO");
|
||||
}
|
||||
buf_u32(out, 0); // NET_API_STATUS return: NERR_Success
|
||||
|
||||
uint32_t stub_len_out = out->len - stub_start;
|
||||
memcpy(out->data + 16, &stub_len_out, 4);
|
||||
fixup_frag_length(out);
|
||||
pipe->reply_pending = true;
|
||||
(void)stub; (void)stub_len;
|
||||
}
|
||||
|
||||
// ---- entry points -------------------------------------------------------
|
||||
|
||||
int rpc_pipe_write(const smb2_file_id file_id, const uint8_t *data, uint32_t len)
|
||||
{
|
||||
rpc_pipe_state *pipe = find_pipe(file_id);
|
||||
if (!pipe) return -1;
|
||||
if (!data) return -1;
|
||||
if (len < 16)
|
||||
{
|
||||
ESP_LOGW(TAG, "rpc_pipe_write: PDU too short (%u)", (unsigned)len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t ptype = data[2];
|
||||
uint32_t call_id; memcpy(&call_id, data + 12, 4);
|
||||
pipe->call_id = call_id;
|
||||
|
||||
const uint8_t *body = data + 16;
|
||||
uint32_t body_len = len - 16;
|
||||
|
||||
if (ptype == RPC_PTYPE_BIND)
|
||||
{
|
||||
handle_bind(pipe, body, body_len);
|
||||
}
|
||||
else if (ptype == RPC_PTYPE_REQUEST)
|
||||
{
|
||||
if (body_len < 8)
|
||||
{
|
||||
ESP_LOGW(TAG, "rpc request: body too short");
|
||||
return -1;
|
||||
}
|
||||
uint16_t context_id; memcpy(&context_id, body + 4, 2);
|
||||
uint16_t opnum; memcpy(&opnum, body + 6, 2);
|
||||
const uint8_t *stub = body + 8;
|
||||
uint32_t stub_len = body_len - 8;
|
||||
|
||||
if (pipe->type == RPC_PIPE_WKSSVC)
|
||||
wkssvc_dispatch(pipe, opnum, context_id, stub, stub_len);
|
||||
else if (pipe->type == RPC_PIPE_SRVSVC)
|
||||
srvsvc_dispatch(pipe, opnum, context_id, stub, stub_len);
|
||||
else
|
||||
ESP_LOGW(TAG, "rpc request: pipe type=%d not wkssvc/srvsvc, opnum=%u ignored", (int)pipe->type, (unsigned)opnum);
|
||||
|
||||
ESP_LOGI(TAG, "rpc request: type=%d opnum=%u reply_pending=%d reply_len=%u",
|
||||
(int)pipe->type, (unsigned)opnum, (int)pipe->reply_pending, (unsigned)pipe->reply.len);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGW(TAG, "rpc_pipe_write: unhandled ptype=%u", (unsigned)ptype);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rpc_pipe_read(const smb2_file_id file_id, const uint8_t **out_data, uint32_t *out_len)
|
||||
{
|
||||
rpc_pipe_state *pipe = find_pipe(file_id);
|
||||
if (!pipe || !pipe->reply_pending)
|
||||
{
|
||||
if (out_len) *out_len = 0;
|
||||
return 0;
|
||||
}
|
||||
*out_data = pipe->reply.data;
|
||||
*out_len = pipe->reply.len;
|
||||
pipe->reply_pending = false;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
// Minimal DCE/RPC-over-named-pipe support for the wkssvc/srvsvc endpoints
|
||||
// Windows Explorer probes before it will browse an SMB share (see the SMB
|
||||
// server plan). Only enough of MS-RPCE/MS-WKST/MS-SRVS is implemented to
|
||||
// satisfy that initial probe (bind + one or two read-only info calls) -
|
||||
// this is not a general-purpose RPC server.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <time.h> // smb2.h uses time_t without including <ctime> itself
|
||||
#include "smb2.h"
|
||||
|
||||
enum rpc_pipe_type
|
||||
{
|
||||
RPC_PIPE_NONE = 0,
|
||||
RPC_PIPE_WKSSVC,
|
||||
RPC_PIPE_SRVSVC,
|
||||
};
|
||||
|
||||
rpc_pipe_type rpc_pipe_type_for_name(const char *name);
|
||||
|
||||
// file_id is the SMB2 file id (16 bytes) the CREATE handler assigned - used
|
||||
// to correlate the WRITE (request) and READ (response) that make up one
|
||||
// RPC round trip, and to remember which pipe (and thus which RPC
|
||||
// interface) a given open handle is bound to.
|
||||
// owner identifies which connection this pipe belongs to (see
|
||||
// rpc_pipe_reset_for_owner() below) - libsmb2 hands every handler its
|
||||
// struct smb2_context* already, just thread it through.
|
||||
void rpc_pipe_open(const smb2_file_id file_id, rpc_pipe_type type, struct smb2_context *owner);
|
||||
void rpc_pipe_close(const smb2_file_id file_id);
|
||||
|
||||
// Frees every pipe slot belonging to `owner` - for use when THAT specific
|
||||
// client connection is torn down (a dropped/reset TCP connection never
|
||||
// sends a matching CLOSE for whatever pipes were open). Scoped to one
|
||||
// owner, not a blanket reset: this table is global and can hold slots for
|
||||
// more than one concurrent connection (e.g. Explorer's own IPC$ probe
|
||||
// connections briefly overlapping the one it actually browses with) -
|
||||
// wiping every connection's pipes just because one of them disconnected
|
||||
// was confirmed on real hardware to force the *other*, still-active
|
||||
// connection to restart its RPC bind from scratch in an infinite loop.
|
||||
void rpc_pipe_reset_for_owner(struct smb2_context *owner);
|
||||
|
||||
// True if file_id is a currently-open RPC pipe handle. QUERY_INFO on a
|
||||
// named pipe must report zero size - Windows' redirector uses a nonzero
|
||||
// end_of_file as a sign this isn't really a pipe and quietly gives up
|
||||
// without ever attempting the RPC bind (confirmed by packet capture: it
|
||||
// was doing CREATE -> QUERY_INFO -> CLOSE in a loop, never WRITE).
|
||||
bool rpc_pipe_is_open(const smb2_file_id file_id);
|
||||
|
||||
// Feeds a WRITE's payload (a DCE/RPC PDU: bind or request) to the pipe
|
||||
// identified by file_id. Builds the reply PDU internally; returns 0 on
|
||||
// success (including "we didn't understand this, queued a fault reply"),
|
||||
// <0 if file_id isn't a known open RPC pipe.
|
||||
int rpc_pipe_write(const smb2_file_id file_id, const uint8_t *data, uint32_t len);
|
||||
|
||||
// Retrieves the reply PDU queued by the preceding write, if any. *out_data
|
||||
// points into internal storage valid until the next write/close on this
|
||||
// file_id. Returns 0 with out_len=0 if nothing is queued.
|
||||
int rpc_pipe_read(const smb2_file_id file_id, const uint8_t **out_data, uint32_t *out_len);
|
||||
@@ -0,0 +1,149 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "nvs_params.h"
|
||||
|
||||
app_params_t app_params;
|
||||
|
||||
char NVS_PARAMS_NS[] = "appcfg";
|
||||
char NVS_KEY_USB_MODE[] = "usb_mode";
|
||||
char NVS_KEY_WIFI_MODE[] = "wifi_mode";
|
||||
char NVS_KEY_WIFI_AP[] = "wifi_ap";
|
||||
char NVS_KEY_WIFI_PSW[] = "wifi_psw";
|
||||
|
||||
void initialize_nvs()
|
||||
{
|
||||
esp_err_t err = nvs_flash_init();
|
||||
log_sram_used(__FILE_NAME__ ": nvs_flash_init");
|
||||
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
|
||||
{
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
err = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
log_sram_used(__FILE_NAME__ ": initialize_nvs end");
|
||||
}
|
||||
|
||||
void app_params_set_defaults()
|
||||
{
|
||||
memset(&app_params, 0, sizeof(app_params));
|
||||
}
|
||||
|
||||
esp_err_t app_params_load_u8(nvs_handle_t h, const char *key, uint8_t *value)
|
||||
{
|
||||
esp_err_t err = nvs_get_u8(h, key, value);
|
||||
if (err == ESP_ERR_NVS_NOT_FOUND) return ESP_OK;
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t app_params_load_str(nvs_handle_t h, const char *key, char *buf, size_t buf_size)
|
||||
{
|
||||
size_t size = buf_size;
|
||||
esp_err_t err = nvs_get_str(h, key, buf, &size);
|
||||
if (err == ESP_ERR_NVS_NOT_FOUND) return ESP_OK;
|
||||
if (err == ESP_ERR_NVS_INVALID_LENGTH)
|
||||
{
|
||||
buf[0] = 0;
|
||||
return ESP_OK;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t app_params_load()
|
||||
{
|
||||
app_params_set_defaults();
|
||||
|
||||
nvs_handle_t h = 0;
|
||||
esp_err_t err = nvs_open(NVS_PARAMS_NS, NVS_READONLY, &h);
|
||||
if (err == ESP_ERR_NVS_NOT_FOUND) return ESP_OK;
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
err = app_params_load_u8(h, NVS_KEY_USB_MODE, &app_params.usb_mode);
|
||||
if (err != ESP_OK) goto end;
|
||||
|
||||
err = app_params_load_u8(h, NVS_KEY_WIFI_MODE, &app_params.wifi_mode);
|
||||
if (err != ESP_OK) goto end;
|
||||
|
||||
err = app_params_load_str(h, NVS_KEY_WIFI_AP, app_params.wifi_ap, sizeof(app_params.wifi_ap));
|
||||
if (err != ESP_OK) goto end;
|
||||
|
||||
err = app_params_load_str(h, NVS_KEY_WIFI_PSW, app_params.wifi_psw, sizeof(app_params.wifi_psw));
|
||||
if (err != ESP_OK) goto end;
|
||||
|
||||
end:
|
||||
nvs_close(h);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t app_params_save()
|
||||
{
|
||||
nvs_handle_t h = 0;
|
||||
esp_err_t err = nvs_open(NVS_PARAMS_NS, NVS_READWRITE, &h);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
err = nvs_set_u8(h, NVS_KEY_USB_MODE, app_params.usb_mode);
|
||||
if (err != ESP_OK) goto end;
|
||||
|
||||
err = nvs_set_u8(h, NVS_KEY_WIFI_MODE, app_params.wifi_mode);
|
||||
if (err != ESP_OK) goto end;
|
||||
|
||||
err = nvs_set_str(h, NVS_KEY_WIFI_AP, app_params.wifi_ap);
|
||||
if (err != ESP_OK) goto end;
|
||||
|
||||
err = nvs_set_str(h, NVS_KEY_WIFI_PSW, app_params.wifi_psw);
|
||||
if (err != ESP_OK) goto end;
|
||||
|
||||
err = nvs_commit(h);
|
||||
|
||||
end:
|
||||
nvs_close(h);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t app_params_reset()
|
||||
{
|
||||
app_params_set_defaults();
|
||||
return app_params_save();
|
||||
}
|
||||
|
||||
bool app_params_set_by_name(const char *name, const char *value)
|
||||
{
|
||||
char *endp = NULL;
|
||||
unsigned long v = 0;
|
||||
|
||||
if (!strcmp(name, "usb_mode"))
|
||||
{
|
||||
v = strtoul(value, &endp, 0);
|
||||
if (!endp || *endp || v > 255) return false;
|
||||
app_params.usb_mode = (uint8_t)v;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(name, "wifi_mode"))
|
||||
{
|
||||
v = strtoul(value, &endp, 0);
|
||||
if (!endp || *endp || v > 255) return false;
|
||||
app_params.wifi_mode = (uint8_t)v;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(name, "wifi_ap"))
|
||||
{
|
||||
strlcpy(app_params.wifi_ap, value, sizeof(app_params.wifi_ap));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(name, "wifi_psw"))
|
||||
{
|
||||
strlcpy(app_params.wifi_psw, value, sizeof(app_params.wifi_psw));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
struct app_params_t
|
||||
{
|
||||
uint8_t usb_mode;
|
||||
uint8_t wifi_mode;
|
||||
char wifi_ap[64];
|
||||
char wifi_psw[64];
|
||||
};
|
||||
|
||||
extern app_params_t app_params;
|
||||
|
||||
void initialize_nvs();
|
||||
void app_params_set_defaults();
|
||||
esp_err_t app_params_load();
|
||||
esp_err_t app_params_save();
|
||||
esp_err_t app_params_reset();
|
||||
bool app_params_set_by_name(const char *name, const char *value);
|
||||
+4620
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "main.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum
|
||||
{
|
||||
OPL_MODE_OFF = 0,
|
||||
OPL_MODE_YMF262,
|
||||
OPL_MODE_YMF278
|
||||
} OPL_MODE;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
OPL_FM_RENDER_OPL3_FULL = 0,
|
||||
OPL_FM_RENDER_OPL2_MELODY
|
||||
} OPL_FM_RENDER_MODE;
|
||||
|
||||
#define OPL_PORT_FM_REG1 0x00C4
|
||||
#define OPL_PORT_FM_DAT1 0x00C5
|
||||
#define OPL_PORT_FM_REG2 0x00C6
|
||||
#define OPL_PORT_FM_DAT2 0x00C7
|
||||
#define OPL_PORT_WAVE_REG 0x007E
|
||||
#define OPL_PORT_WAVE_DAT 0x007F
|
||||
|
||||
#define OPL_ROM_PART_LABEL "oplrom"
|
||||
#define OPL_ROM_PART_SUBTYPE 0x85
|
||||
#define OPL_ROM_SIZE 0x200000
|
||||
#define OPL_YMF278_DEFAULT_RAM_SIZE 0x400000
|
||||
#define OPL_FM_BANK_COUNT 2
|
||||
#define OPL_FM_REG_COUNT 256
|
||||
#define OPL_WAVE_REG_COUNT 256
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u16 reg;
|
||||
u8 value;
|
||||
} OPL_FM_REG_WRITE;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
OPL_MODE mode;
|
||||
u8 enabled;
|
||||
size_t rom_size;
|
||||
size_t ram_size;
|
||||
u8 fm_latch_bank;
|
||||
u8 fm_latch[2];
|
||||
u8 wave_latch;
|
||||
u8 status;
|
||||
u32 queued_writes;
|
||||
} OPL_INFO;
|
||||
|
||||
esp_err_t opl_enable(OPL_MODE mode, size_t ram_size);
|
||||
void opl_disable();
|
||||
void opl_reset();
|
||||
int opl_is_enabled();
|
||||
esp_err_t opl_get_info(OPL_INFO *info);
|
||||
|
||||
u8 opl_read_status();
|
||||
u8 opl_read_port(u16 port);
|
||||
void opl_write_port(u16 port, u8 value);
|
||||
esp_err_t opl_queue_write_port(u16 port, u8 value);
|
||||
esp_err_t opl_queue_write_fm_reg(u16 reg, u8 value);
|
||||
esp_err_t opl_queue_write_wave_reg(u8 reg, u8 value);
|
||||
|
||||
u8 opl_read_fm_reg(u16 reg);
|
||||
void opl_write_fm_reg(u16 reg, u8 value);
|
||||
esp_err_t opl_write_fm_regs(const OPL_FM_REG_WRITE *writes, size_t count);
|
||||
u8 opl_read_wave_reg(u8 reg);
|
||||
void opl_write_wave_reg(u8 reg, u8 value);
|
||||
|
||||
esp_err_t opl_set_fm_render_mode(OPL_FM_RENDER_MODE mode);
|
||||
esp_err_t opl_set_volume(u8 volume);
|
||||
u8 opl_get_volume();
|
||||
void opl_render(float *mix, int sample_count, int sample_rate);
|
||||
void opl_console_register_system_commands();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,922 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_console.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
|
||||
#include "ps2_mouse.h"
|
||||
|
||||
const gpio_num_t PS2_MOUSE_GPIO_DATA = GPIO_NUM_5;
|
||||
const gpio_num_t PS2_MOUSE_GPIO_CLK = GPIO_NUM_7;
|
||||
|
||||
enum
|
||||
{
|
||||
PS2_TX_FIFO_SIZE = 64,
|
||||
|
||||
PS2_CLK_LOW_US = 35,
|
||||
PS2_CLK_HIGH_US = 35,
|
||||
PS2_TX_HIGH_HOLD_US = 15,
|
||||
PS2_TX_SETUP_US = 20,
|
||||
PS2_TX_INTERBYTE_US = 80,
|
||||
PS2_HOST_RX_START_US = 30,
|
||||
PS2_HOST_RX_SAMPLE_US = 18,
|
||||
PS2_HOST_ACK_SETUP_US = 15,
|
||||
PS2_HOST_INHIBIT_US = 120,
|
||||
PS2_TX_RETRY_US = 200,
|
||||
};
|
||||
|
||||
enum ps2_bus_mode_t
|
||||
{
|
||||
PS2_BUS_IDLE = 0,
|
||||
PS2_BUS_DEV_TX,
|
||||
PS2_BUS_HOST_RX,
|
||||
PS2_BUS_HOST_ACK,
|
||||
};
|
||||
|
||||
enum ps2_timer_evt_t
|
||||
{
|
||||
PS2_TIMER_EVT_NONE = 0,
|
||||
PS2_TIMER_EVT_DEV_TX_FALL,
|
||||
PS2_TIMER_EVT_DEV_TX_RISE,
|
||||
PS2_TIMER_EVT_DEV_TX_NEXT,
|
||||
PS2_TIMER_EVT_DEV_TX_DONE,
|
||||
PS2_TIMER_EVT_HOST_RX_FALL,
|
||||
PS2_TIMER_EVT_HOST_RX_RISE,
|
||||
PS2_TIMER_EVT_HOST_RX_SAMPLE,
|
||||
PS2_TIMER_EVT_HOST_ACK_PREPARE,
|
||||
PS2_TIMER_EVT_HOST_ACK_FALL,
|
||||
PS2_TIMER_EVT_HOST_ACK_RISE,
|
||||
PS2_TIMER_EVT_HOST_ACK_DONE,
|
||||
PS2_TIMER_EVT_TX_RETRY,
|
||||
};
|
||||
|
||||
gptimer_handle_t ps2_mouse_timer = NULL;
|
||||
portMUX_TYPE ps2_mouse_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
bool ps2_mouse_gpio_isr_service_ready = false;
|
||||
|
||||
volatile bool ps2_mouse_active = false;
|
||||
|
||||
volatile bool ps2_mouse_streaming = false;
|
||||
volatile bool ps2_mouse_remote_mode = false;
|
||||
volatile bool ps2_mouse_scaling21 = false;
|
||||
volatile bool ps2_mouse_expect_param = false;
|
||||
volatile uint8_t ps2_mouse_pending_cmd = 0;
|
||||
volatile uint8_t ps2_mouse_sample_rate = 100;
|
||||
volatile uint8_t ps2_mouse_resolution = 2;
|
||||
|
||||
volatile int ps2_mouse_dx_accum = 0;
|
||||
volatile int ps2_mouse_dy_accum = 0;
|
||||
volatile uint8_t ps2_mouse_buttons = 0;
|
||||
volatile bool ps2_mouse_motion_pending = false;
|
||||
|
||||
volatile uint8_t ps2_mouse_tx_fifo[PS2_TX_FIFO_SIZE];
|
||||
volatile unsigned ps2_mouse_tx_rd = 0;
|
||||
volatile unsigned ps2_mouse_tx_wr = 0;
|
||||
volatile unsigned ps2_mouse_tx_count = 0;
|
||||
volatile bool ps2_mouse_tx_resume_valid = false;
|
||||
volatile uint8_t ps2_mouse_tx_resume_byte = 0;
|
||||
|
||||
volatile ps2_bus_mode_t ps2_mouse_bus_mode = PS2_BUS_IDLE;
|
||||
volatile ps2_timer_evt_t ps2_mouse_timer_evt = PS2_TIMER_EVT_NONE;
|
||||
|
||||
volatile uint8_t ps2_mouse_tx_byte = 0;
|
||||
volatile uint8_t ps2_mouse_tx_parity = 1;
|
||||
volatile int ps2_mouse_tx_bit = 0;
|
||||
|
||||
volatile uint8_t ps2_mouse_rx_byte = 0;
|
||||
volatile uint8_t ps2_mouse_rx_parity = 1;
|
||||
volatile int ps2_mouse_rx_bit = 0;
|
||||
volatile bool ps2_mouse_rx_start_ok = false;
|
||||
volatile bool ps2_mouse_rx_parity_ok = false;
|
||||
volatile bool ps2_mouse_rx_stop_ok = false;
|
||||
volatile uint8_t ps2_mouse_rx_done_byte = 0;
|
||||
volatile bool ps2_mouse_rx_frame_ok = false;
|
||||
|
||||
volatile uint32_t ps2_mouse_clk_low_since_us = 0;
|
||||
|
||||
void IRAM_ATTR ps2_mouse_clk_low()
|
||||
{
|
||||
gpio_set_level(PS2_MOUSE_GPIO_CLK, 0);
|
||||
}
|
||||
|
||||
void IRAM_ATTR ps2_mouse_clk_release()
|
||||
{
|
||||
gpio_set_level(PS2_MOUSE_GPIO_CLK, 1);
|
||||
}
|
||||
|
||||
void IRAM_ATTR ps2_mouse_data_low()
|
||||
{
|
||||
gpio_set_level(PS2_MOUSE_GPIO_DATA, 0);
|
||||
}
|
||||
|
||||
void IRAM_ATTR ps2_mouse_data_release()
|
||||
{
|
||||
gpio_set_level(PS2_MOUSE_GPIO_DATA, 1);
|
||||
}
|
||||
|
||||
int IRAM_ATTR ps2_mouse_clk_level()
|
||||
{
|
||||
return gpio_get_level(PS2_MOUSE_GPIO_CLK);
|
||||
}
|
||||
|
||||
int IRAM_ATTR ps2_mouse_data_level()
|
||||
{
|
||||
return gpio_get_level(PS2_MOUSE_GPIO_DATA);
|
||||
}
|
||||
|
||||
int IRAM_ATTR ps2_mouse_line_idle()
|
||||
{
|
||||
return ps2_mouse_clk_level() && ps2_mouse_data_level();
|
||||
}
|
||||
|
||||
uint32_t IRAM_ATTR ps2_mouse_get_time_us_isr()
|
||||
{
|
||||
uint64_t now = 0;
|
||||
|
||||
if (!ps2_mouse_timer)
|
||||
return 0;
|
||||
|
||||
gptimer_get_raw_count(ps2_mouse_timer, &now);
|
||||
return (uint32_t)now;
|
||||
}
|
||||
|
||||
void IRAM_ATTR ps2_mouse_timer_schedule_isr(ps2_timer_evt_t evt, uint32_t delta_us)
|
||||
{
|
||||
uint64_t now = 0;
|
||||
gptimer_alarm_config_t alarm = {};
|
||||
|
||||
if (!ps2_mouse_timer)
|
||||
return;
|
||||
|
||||
gptimer_get_raw_count(ps2_mouse_timer, &now);
|
||||
|
||||
ps2_mouse_timer_evt = evt;
|
||||
alarm.alarm_count = now + delta_us;
|
||||
alarm.flags.auto_reload_on_alarm = false;
|
||||
|
||||
gptimer_set_alarm_action(ps2_mouse_timer, &alarm);
|
||||
}
|
||||
|
||||
void IRAM_ATTR ps2_mouse_reset_device_state_isr()
|
||||
{
|
||||
ps2_mouse_streaming = false;
|
||||
ps2_mouse_remote_mode = false;
|
||||
ps2_mouse_scaling21 = false;
|
||||
ps2_mouse_expect_param = false;
|
||||
ps2_mouse_pending_cmd = 0;
|
||||
ps2_mouse_sample_rate = 100;
|
||||
ps2_mouse_resolution = 2;
|
||||
|
||||
ps2_mouse_dx_accum = 0;
|
||||
ps2_mouse_dy_accum = 0;
|
||||
ps2_mouse_buttons = 0;
|
||||
ps2_mouse_motion_pending = false;
|
||||
}
|
||||
|
||||
void IRAM_ATTR ps2_mouse_reset_protocol_state_isr()
|
||||
{
|
||||
ps2_mouse_reset_device_state_isr();
|
||||
|
||||
ps2_mouse_tx_rd = 0;
|
||||
ps2_mouse_tx_wr = 0;
|
||||
ps2_mouse_tx_count = 0;
|
||||
ps2_mouse_tx_resume_valid = false;
|
||||
ps2_mouse_tx_resume_byte = 0;
|
||||
|
||||
ps2_mouse_bus_mode = PS2_BUS_IDLE;
|
||||
ps2_mouse_timer_evt = PS2_TIMER_EVT_NONE;
|
||||
|
||||
ps2_mouse_tx_byte = 0;
|
||||
ps2_mouse_tx_parity = 1;
|
||||
ps2_mouse_tx_bit = 0;
|
||||
|
||||
ps2_mouse_rx_byte = 0;
|
||||
ps2_mouse_rx_parity = 1;
|
||||
ps2_mouse_rx_bit = 0;
|
||||
ps2_mouse_rx_start_ok = false;
|
||||
ps2_mouse_rx_parity_ok = false;
|
||||
ps2_mouse_rx_stop_ok = false;
|
||||
ps2_mouse_rx_done_byte = 0;
|
||||
ps2_mouse_rx_frame_ok = false;
|
||||
|
||||
ps2_mouse_clk_low_since_us = 0;
|
||||
}
|
||||
|
||||
int IRAM_ATTR ps2_mouse_tx_push_isr(uint8_t v)
|
||||
{
|
||||
if (ps2_mouse_tx_count >= PS2_TX_FIFO_SIZE)
|
||||
return 0;
|
||||
|
||||
ps2_mouse_tx_fifo[ps2_mouse_tx_wr] = v;
|
||||
ps2_mouse_tx_wr = ps2_mouse_tx_wr + 1;
|
||||
if (ps2_mouse_tx_wr >= PS2_TX_FIFO_SIZE)
|
||||
ps2_mouse_tx_wr = 0;
|
||||
|
||||
ps2_mouse_tx_count = ps2_mouse_tx_count + 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int IRAM_ATTR ps2_mouse_tx_pop_isr(uint8_t *v)
|
||||
{
|
||||
if (ps2_mouse_tx_resume_valid)
|
||||
{
|
||||
*v = ps2_mouse_tx_resume_byte;
|
||||
ps2_mouse_tx_resume_valid = false;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!ps2_mouse_tx_count)
|
||||
return 0;
|
||||
|
||||
*v = ps2_mouse_tx_fifo[ps2_mouse_tx_rd];
|
||||
ps2_mouse_tx_rd = ps2_mouse_tx_rd + 1;
|
||||
if (ps2_mouse_tx_rd >= PS2_TX_FIFO_SIZE)
|
||||
ps2_mouse_tx_rd = 0;
|
||||
|
||||
ps2_mouse_tx_count = ps2_mouse_tx_count - 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int IRAM_ATTR ps2_mouse_clamp_9bit(int v)
|
||||
{
|
||||
if (v > 255)
|
||||
return 255;
|
||||
if (v < -255)
|
||||
return -255;
|
||||
return v;
|
||||
}
|
||||
|
||||
void IRAM_ATTR ps2_mouse_build_packet_isr(uint8_t packet[3], bool clear_motion)
|
||||
{
|
||||
int dx = ps2_mouse_clamp_9bit(ps2_mouse_dx_accum);
|
||||
int dy = ps2_mouse_clamp_9bit(ps2_mouse_dy_accum);
|
||||
|
||||
packet[0] = 0x08 | (ps2_mouse_buttons & 0x07);
|
||||
|
||||
if (dx < 0)
|
||||
packet[0] |= 0x10;
|
||||
|
||||
if (dy < 0)
|
||||
packet[0] |= 0x20;
|
||||
|
||||
if (dx != ps2_mouse_dx_accum)
|
||||
packet[0] |= 0x40;
|
||||
|
||||
if (dy != ps2_mouse_dy_accum)
|
||||
packet[0] |= 0x80;
|
||||
|
||||
packet[1] = (uint8_t)(dx & 0xFF);
|
||||
packet[2] = (uint8_t)(dy & 0xFF);
|
||||
|
||||
if (clear_motion)
|
||||
{
|
||||
ps2_mouse_dx_accum = 0;
|
||||
ps2_mouse_dy_accum = 0;
|
||||
ps2_mouse_motion_pending = false;
|
||||
}
|
||||
}
|
||||
|
||||
void IRAM_ATTR ps2_mouse_queue_motion_if_possible_isr()
|
||||
{
|
||||
uint8_t packet[3];
|
||||
|
||||
if (!ps2_mouse_streaming)
|
||||
return;
|
||||
|
||||
if (ps2_mouse_remote_mode)
|
||||
return;
|
||||
|
||||
if (!ps2_mouse_motion_pending)
|
||||
return;
|
||||
|
||||
if (ps2_mouse_tx_count > (PS2_TX_FIFO_SIZE - 3))
|
||||
return;
|
||||
|
||||
ps2_mouse_build_packet_isr(packet, true);
|
||||
|
||||
ps2_mouse_tx_push_isr(packet[0]);
|
||||
ps2_mouse_tx_push_isr(packet[1]);
|
||||
ps2_mouse_tx_push_isr(packet[2]);
|
||||
}
|
||||
|
||||
int IRAM_ATTR ps2_mouse_get_tx_bit_isr(int bit_index)
|
||||
{
|
||||
if (bit_index == 0)
|
||||
return 0;
|
||||
|
||||
if (bit_index >= 1 && bit_index <= 8)
|
||||
return (ps2_mouse_tx_byte >> (bit_index - 1)) & 1;
|
||||
|
||||
if (bit_index == 9)
|
||||
return ps2_mouse_tx_parity;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void IRAM_ATTR ps2_mouse_drive_tx_bit_isr(int bit_index)
|
||||
{
|
||||
if (ps2_mouse_get_tx_bit_isr(bit_index))
|
||||
ps2_mouse_data_release();
|
||||
else
|
||||
ps2_mouse_data_low();
|
||||
}
|
||||
|
||||
void IRAM_ATTR ps2_mouse_prepare_tx_byte_isr(uint8_t v)
|
||||
{
|
||||
ps2_mouse_tx_byte = v;
|
||||
ps2_mouse_tx_parity = 1;
|
||||
for (int i = 0; i < 8; i++)
|
||||
ps2_mouse_tx_parity ^= (uint8_t)((v >> i) & 1);
|
||||
|
||||
ps2_mouse_tx_bit = 0;
|
||||
ps2_mouse_bus_mode = PS2_BUS_DEV_TX;
|
||||
|
||||
ps2_mouse_clk_release();
|
||||
ps2_mouse_drive_tx_bit_isr(0);
|
||||
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_DEV_TX_FALL, PS2_TX_SETUP_US);
|
||||
}
|
||||
|
||||
int IRAM_ATTR ps2_mouse_host_inhibit_active_isr()
|
||||
{
|
||||
uint32_t now;
|
||||
|
||||
if (ps2_mouse_clk_level())
|
||||
return 0;
|
||||
|
||||
now = ps2_mouse_get_time_us_isr();
|
||||
if ((uint32_t)(now - ps2_mouse_clk_low_since_us) < PS2_HOST_INHIBIT_US)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void IRAM_ATTR ps2_mouse_dev_tx_abort_isr()
|
||||
{
|
||||
if (ps2_mouse_bus_mode != PS2_BUS_DEV_TX)
|
||||
return;
|
||||
|
||||
ps2_mouse_tx_resume_valid = true;
|
||||
ps2_mouse_tx_resume_byte = ps2_mouse_tx_byte;
|
||||
ps2_mouse_data_release();
|
||||
ps2_mouse_clk_release();
|
||||
ps2_mouse_bus_mode = PS2_BUS_IDLE;
|
||||
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_TX_RETRY, PS2_TX_RETRY_US);
|
||||
}
|
||||
|
||||
void IRAM_ATTR ps2_mouse_try_start_tx_isr()
|
||||
{
|
||||
uint8_t v = 0;
|
||||
|
||||
if (!ps2_mouse_active)
|
||||
return;
|
||||
|
||||
if (ps2_mouse_bus_mode != PS2_BUS_IDLE)
|
||||
return;
|
||||
|
||||
ps2_mouse_queue_motion_if_possible_isr();
|
||||
|
||||
if (!ps2_mouse_tx_resume_valid && !ps2_mouse_tx_count)
|
||||
return;
|
||||
|
||||
if (!ps2_mouse_line_idle())
|
||||
{
|
||||
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_TX_RETRY, PS2_TX_RETRY_US);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ps2_mouse_tx_pop_isr(&v))
|
||||
return;
|
||||
|
||||
ps2_mouse_prepare_tx_byte_isr(v);
|
||||
}
|
||||
|
||||
void IRAM_ATTR ps2_mouse_start_host_rx_isr()
|
||||
{
|
||||
if (!ps2_mouse_active)
|
||||
return;
|
||||
|
||||
if (ps2_mouse_bus_mode != PS2_BUS_IDLE)
|
||||
return;
|
||||
|
||||
ps2_mouse_bus_mode = PS2_BUS_HOST_RX;
|
||||
|
||||
ps2_mouse_rx_byte = 0;
|
||||
ps2_mouse_rx_parity = 1;
|
||||
ps2_mouse_rx_bit = 0;
|
||||
ps2_mouse_rx_start_ok = true;
|
||||
ps2_mouse_rx_parity_ok = false;
|
||||
ps2_mouse_rx_stop_ok = false;
|
||||
ps2_mouse_rx_done_byte = 0;
|
||||
ps2_mouse_rx_frame_ok = false;
|
||||
|
||||
ps2_mouse_data_release();
|
||||
ps2_mouse_clk_release();
|
||||
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_HOST_RX_FALL, PS2_HOST_RX_START_US);
|
||||
}
|
||||
|
||||
void IRAM_ATTR ps2_mouse_host_rx_sample_isr()
|
||||
{
|
||||
int dat = ps2_mouse_data_level();
|
||||
|
||||
if (ps2_mouse_rx_bit >= 0 && ps2_mouse_rx_bit <= 7)
|
||||
{
|
||||
if (dat)
|
||||
ps2_mouse_rx_byte |= (uint8_t)(1U << ps2_mouse_rx_bit);
|
||||
|
||||
ps2_mouse_rx_parity ^= (uint8_t)dat;
|
||||
}
|
||||
else if (ps2_mouse_rx_bit == 8)
|
||||
{
|
||||
ps2_mouse_rx_parity_ok = ((uint8_t)dat == ps2_mouse_rx_parity);
|
||||
}
|
||||
else if (ps2_mouse_rx_bit == 9)
|
||||
{
|
||||
ps2_mouse_rx_stop_ok = (dat == 1);
|
||||
ps2_mouse_rx_done_byte = ps2_mouse_rx_byte;
|
||||
ps2_mouse_rx_frame_ok = ps2_mouse_rx_start_ok && ps2_mouse_rx_parity_ok && ps2_mouse_rx_stop_ok;
|
||||
}
|
||||
|
||||
ps2_mouse_rx_bit = ps2_mouse_rx_bit + 1;
|
||||
}
|
||||
|
||||
void IRAM_ATTR ps2_mouse_handle_cmd_isr(uint8_t value)
|
||||
{
|
||||
uint8_t packet[3];
|
||||
uint8_t status = 0;
|
||||
|
||||
if (ps2_mouse_expect_param)
|
||||
{
|
||||
ps2_mouse_expect_param = false;
|
||||
|
||||
if (ps2_mouse_pending_cmd == 0xF3)
|
||||
ps2_mouse_sample_rate = value;
|
||||
|
||||
if (ps2_mouse_pending_cmd == 0xE8)
|
||||
ps2_mouse_resolution = value & 0x03;
|
||||
|
||||
ps2_mouse_pending_cmd = 0;
|
||||
ps2_mouse_tx_push_isr(0xFA);
|
||||
ps2_mouse_try_start_tx_isr();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case 0xFF:
|
||||
ps2_mouse_reset_device_state_isr();
|
||||
ps2_mouse_tx_push_isr(0xFA);
|
||||
ps2_mouse_tx_push_isr(0xAA);
|
||||
ps2_mouse_tx_push_isr(0x00);
|
||||
break;
|
||||
|
||||
case 0xF6:
|
||||
ps2_mouse_reset_device_state_isr();
|
||||
ps2_mouse_tx_push_isr(0xFA);
|
||||
break;
|
||||
|
||||
case 0xF5:
|
||||
ps2_mouse_streaming = false;
|
||||
ps2_mouse_tx_push_isr(0xFA);
|
||||
break;
|
||||
|
||||
case 0xF4:
|
||||
ps2_mouse_streaming = true;
|
||||
ps2_mouse_remote_mode = false;
|
||||
ps2_mouse_tx_push_isr(0xFA);
|
||||
ps2_mouse_queue_motion_if_possible_isr();
|
||||
break;
|
||||
|
||||
case 0xF2:
|
||||
ps2_mouse_tx_push_isr(0xFA);
|
||||
ps2_mouse_tx_push_isr(0x00);
|
||||
break;
|
||||
|
||||
case 0xEB:
|
||||
ps2_mouse_tx_push_isr(0xFA);
|
||||
ps2_mouse_build_packet_isr(packet, true);
|
||||
ps2_mouse_tx_push_isr(packet[0]);
|
||||
ps2_mouse_tx_push_isr(packet[1]);
|
||||
ps2_mouse_tx_push_isr(packet[2]);
|
||||
break;
|
||||
|
||||
case 0xEA:
|
||||
ps2_mouse_remote_mode = false;
|
||||
ps2_mouse_tx_push_isr(0xFA);
|
||||
break;
|
||||
|
||||
case 0xF0:
|
||||
ps2_mouse_remote_mode = true;
|
||||
ps2_mouse_streaming = false;
|
||||
ps2_mouse_tx_push_isr(0xFA);
|
||||
break;
|
||||
|
||||
case 0xE6:
|
||||
ps2_mouse_scaling21 = false;
|
||||
ps2_mouse_tx_push_isr(0xFA);
|
||||
break;
|
||||
|
||||
case 0xE7:
|
||||
ps2_mouse_scaling21 = true;
|
||||
ps2_mouse_tx_push_isr(0xFA);
|
||||
break;
|
||||
|
||||
case 0xE9:
|
||||
if (ps2_mouse_scaling21)
|
||||
status |= 0x10;
|
||||
if (ps2_mouse_streaming)
|
||||
status |= 0x20;
|
||||
if (ps2_mouse_remote_mode)
|
||||
status |= 0x40;
|
||||
status |= (ps2_mouse_buttons & 0x07);
|
||||
|
||||
ps2_mouse_tx_push_isr(0xFA);
|
||||
ps2_mouse_tx_push_isr(status);
|
||||
ps2_mouse_tx_push_isr(ps2_mouse_resolution);
|
||||
ps2_mouse_tx_push_isr(ps2_mouse_sample_rate);
|
||||
break;
|
||||
|
||||
case 0xF3:
|
||||
ps2_mouse_expect_param = true;
|
||||
ps2_mouse_pending_cmd = 0xF3;
|
||||
ps2_mouse_tx_push_isr(0xFA);
|
||||
break;
|
||||
|
||||
case 0xE8:
|
||||
ps2_mouse_expect_param = true;
|
||||
ps2_mouse_pending_cmd = 0xE8;
|
||||
ps2_mouse_tx_push_isr(0xFA);
|
||||
break;
|
||||
|
||||
default:
|
||||
ps2_mouse_tx_push_isr(0xFE);
|
||||
break;
|
||||
}
|
||||
|
||||
ps2_mouse_try_start_tx_isr();
|
||||
}
|
||||
|
||||
bool IRAM_ATTR ps2_mouse_timer_cb(gptimer_handle_t, const gptimer_alarm_event_data_t *, void *)
|
||||
{
|
||||
portENTER_CRITICAL_ISR(&ps2_mouse_lock);
|
||||
|
||||
if (!ps2_mouse_active)
|
||||
{
|
||||
portEXIT_CRITICAL_ISR(&ps2_mouse_lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (ps2_mouse_timer_evt)
|
||||
{
|
||||
case PS2_TIMER_EVT_DEV_TX_FALL:
|
||||
if (ps2_mouse_bus_mode == PS2_BUS_DEV_TX)
|
||||
{
|
||||
if (ps2_mouse_host_inhibit_active_isr())
|
||||
{
|
||||
ps2_mouse_dev_tx_abort_isr();
|
||||
break;
|
||||
}
|
||||
|
||||
ps2_mouse_clk_low();
|
||||
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_DEV_TX_RISE, PS2_CLK_LOW_US);
|
||||
}
|
||||
break;
|
||||
|
||||
case PS2_TIMER_EVT_DEV_TX_RISE:
|
||||
if (ps2_mouse_bus_mode == PS2_BUS_DEV_TX)
|
||||
{
|
||||
ps2_mouse_clk_release();
|
||||
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_DEV_TX_NEXT, PS2_TX_HIGH_HOLD_US);
|
||||
}
|
||||
break;
|
||||
|
||||
case PS2_TIMER_EVT_DEV_TX_NEXT:
|
||||
if (ps2_mouse_bus_mode == PS2_BUS_DEV_TX)
|
||||
{
|
||||
if (ps2_mouse_host_inhibit_active_isr())
|
||||
{
|
||||
ps2_mouse_dev_tx_abort_isr();
|
||||
break;
|
||||
}
|
||||
|
||||
ps2_mouse_tx_bit = ps2_mouse_tx_bit + 1;
|
||||
if (ps2_mouse_tx_bit <= 10)
|
||||
{
|
||||
ps2_mouse_drive_tx_bit_isr(ps2_mouse_tx_bit);
|
||||
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_DEV_TX_FALL, PS2_TX_SETUP_US);
|
||||
}
|
||||
else
|
||||
{
|
||||
ps2_mouse_data_release();
|
||||
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_DEV_TX_DONE, PS2_TX_SETUP_US);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PS2_TIMER_EVT_DEV_TX_DONE:
|
||||
if (ps2_mouse_bus_mode == PS2_BUS_DEV_TX)
|
||||
{
|
||||
ps2_mouse_data_release();
|
||||
ps2_mouse_clk_release();
|
||||
ps2_mouse_bus_mode = PS2_BUS_IDLE;
|
||||
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_TX_RETRY, PS2_TX_INTERBYTE_US);
|
||||
}
|
||||
break;
|
||||
|
||||
case PS2_TIMER_EVT_HOST_RX_FALL:
|
||||
if (ps2_mouse_bus_mode == PS2_BUS_HOST_RX)
|
||||
{
|
||||
ps2_mouse_data_release();
|
||||
ps2_mouse_clk_low();
|
||||
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_HOST_RX_RISE, PS2_CLK_LOW_US);
|
||||
}
|
||||
break;
|
||||
|
||||
case PS2_TIMER_EVT_HOST_RX_RISE:
|
||||
if (ps2_mouse_bus_mode == PS2_BUS_HOST_RX)
|
||||
{
|
||||
ps2_mouse_clk_release();
|
||||
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_HOST_RX_SAMPLE, PS2_HOST_RX_SAMPLE_US);
|
||||
}
|
||||
break;
|
||||
|
||||
case PS2_TIMER_EVT_HOST_RX_SAMPLE:
|
||||
if (ps2_mouse_bus_mode == PS2_BUS_HOST_RX)
|
||||
{
|
||||
ps2_mouse_host_rx_sample_isr();
|
||||
|
||||
if (ps2_mouse_rx_bit <= 9)
|
||||
{
|
||||
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_HOST_RX_FALL, PS2_CLK_HIGH_US - PS2_HOST_RX_SAMPLE_US);
|
||||
}
|
||||
else
|
||||
{
|
||||
ps2_mouse_bus_mode = PS2_BUS_HOST_ACK;
|
||||
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_HOST_ACK_PREPARE, PS2_CLK_HIGH_US - PS2_HOST_RX_SAMPLE_US);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PS2_TIMER_EVT_HOST_ACK_PREPARE:
|
||||
if (ps2_mouse_bus_mode == PS2_BUS_HOST_ACK)
|
||||
{
|
||||
if (ps2_mouse_rx_frame_ok)
|
||||
ps2_mouse_data_low();
|
||||
else
|
||||
ps2_mouse_data_release();
|
||||
|
||||
ps2_mouse_clk_release();
|
||||
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_HOST_ACK_FALL, PS2_HOST_ACK_SETUP_US);
|
||||
}
|
||||
break;
|
||||
|
||||
case PS2_TIMER_EVT_HOST_ACK_FALL:
|
||||
if (ps2_mouse_bus_mode == PS2_BUS_HOST_ACK)
|
||||
{
|
||||
ps2_mouse_clk_low();
|
||||
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_HOST_ACK_RISE, PS2_CLK_LOW_US);
|
||||
}
|
||||
break;
|
||||
|
||||
case PS2_TIMER_EVT_HOST_ACK_RISE:
|
||||
if (ps2_mouse_bus_mode == PS2_BUS_HOST_ACK)
|
||||
{
|
||||
ps2_mouse_clk_release();
|
||||
ps2_mouse_timer_schedule_isr(PS2_TIMER_EVT_HOST_ACK_DONE, PS2_CLK_HIGH_US);
|
||||
}
|
||||
break;
|
||||
|
||||
case PS2_TIMER_EVT_HOST_ACK_DONE:
|
||||
if (ps2_mouse_bus_mode == PS2_BUS_HOST_ACK)
|
||||
{
|
||||
ps2_mouse_data_release();
|
||||
ps2_mouse_clk_release();
|
||||
ps2_mouse_bus_mode = PS2_BUS_IDLE;
|
||||
|
||||
if (ps2_mouse_rx_frame_ok)
|
||||
ps2_mouse_handle_cmd_isr(ps2_mouse_rx_done_byte);
|
||||
else
|
||||
ps2_mouse_try_start_tx_isr();
|
||||
}
|
||||
break;
|
||||
|
||||
case PS2_TIMER_EVT_TX_RETRY:
|
||||
if (ps2_mouse_bus_mode == PS2_BUS_IDLE)
|
||||
ps2_mouse_try_start_tx_isr();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
portEXIT_CRITICAL_ISR(&ps2_mouse_lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
void IRAM_ATTR ps2_mouse_clk_isr(void *)
|
||||
{
|
||||
int clk;
|
||||
int dat;
|
||||
uint32_t now;
|
||||
uint32_t low_time;
|
||||
|
||||
portENTER_CRITICAL_ISR(&ps2_mouse_lock);
|
||||
|
||||
if (!ps2_mouse_active)
|
||||
{
|
||||
portEXIT_CRITICAL_ISR(&ps2_mouse_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
clk = ps2_mouse_clk_level();
|
||||
dat = ps2_mouse_data_level();
|
||||
now = ps2_mouse_get_time_us_isr();
|
||||
|
||||
if (!clk)
|
||||
{
|
||||
ps2_mouse_clk_low_since_us = now;
|
||||
portEXIT_CRITICAL_ISR(&ps2_mouse_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
low_time = now - ps2_mouse_clk_low_since_us;
|
||||
|
||||
if (ps2_mouse_bus_mode == PS2_BUS_IDLE)
|
||||
{
|
||||
if (low_time >= PS2_HOST_INHIBIT_US && !dat)
|
||||
{
|
||||
ps2_mouse_start_host_rx_isr();
|
||||
portEXIT_CRITICAL_ISR(&ps2_mouse_lock);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
portEXIT_CRITICAL_ISR(&ps2_mouse_lock);
|
||||
}
|
||||
|
||||
void IRAM_ATTR ps2_mouse_data_isr(void *)
|
||||
{
|
||||
}
|
||||
|
||||
esp_err_t ps2_mouse_init()
|
||||
{
|
||||
esp_err_t err;
|
||||
gpio_config_t io = {};
|
||||
gptimer_config_t tcfg = {};
|
||||
gptimer_event_callbacks_t tcbs = {};
|
||||
|
||||
io.pin_bit_mask = (1ULL << PS2_MOUSE_GPIO_DATA) | (1ULL << PS2_MOUSE_GPIO_CLK);
|
||||
io.mode = GPIO_MODE_INPUT_OUTPUT_OD;
|
||||
io.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
io.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io.intr_type = GPIO_INTR_DISABLE;
|
||||
|
||||
err = gpio_config(&io);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
err = gpio_set_pull_mode(PS2_MOUSE_GPIO_DATA, GPIO_PULLUP_ONLY);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
err = gpio_set_pull_mode(PS2_MOUSE_GPIO_CLK, GPIO_PULLUP_ONLY);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
ps2_mouse_data_release();
|
||||
ps2_mouse_clk_release();
|
||||
|
||||
tcfg.clk_src = GPTIMER_CLK_SRC_DEFAULT;
|
||||
tcfg.direction = GPTIMER_COUNT_UP;
|
||||
tcfg.resolution_hz = 1000000;
|
||||
|
||||
err = gptimer_new_timer(&tcfg, &ps2_mouse_timer);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
tcbs.on_alarm = ps2_mouse_timer_cb;
|
||||
|
||||
err = gptimer_register_event_callbacks(ps2_mouse_timer, &tcbs, NULL);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
err = gptimer_enable(ps2_mouse_timer);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
err = gptimer_start(ps2_mouse_timer);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
if (!ps2_mouse_gpio_isr_service_ready)
|
||||
{
|
||||
err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
|
||||
if (err == ESP_ERR_INVALID_STATE)
|
||||
ps2_mouse_gpio_isr_service_ready = true;
|
||||
else if (err != ESP_OK)
|
||||
return err;
|
||||
else
|
||||
ps2_mouse_gpio_isr_service_ready = true;
|
||||
}
|
||||
|
||||
err = gpio_set_intr_type(PS2_MOUSE_GPIO_CLK, GPIO_INTR_ANYEDGE);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
err = gpio_set_intr_type(PS2_MOUSE_GPIO_DATA, GPIO_INTR_ANYEDGE);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
err = gpio_isr_handler_add(PS2_MOUSE_GPIO_CLK, ps2_mouse_clk_isr, NULL);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
err = gpio_isr_handler_add(PS2_MOUSE_GPIO_DATA, ps2_mouse_data_isr, NULL);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
ps2_mouse_clk_low_since_us = 0;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void ps2_mouse_deinit()
|
||||
{
|
||||
gpio_intr_disable(PS2_MOUSE_GPIO_CLK);
|
||||
gpio_intr_disable(PS2_MOUSE_GPIO_DATA);
|
||||
gpio_isr_handler_remove(PS2_MOUSE_GPIO_CLK);
|
||||
gpio_isr_handler_remove(PS2_MOUSE_GPIO_DATA);
|
||||
|
||||
ps2_mouse_data_release();
|
||||
ps2_mouse_clk_release();
|
||||
|
||||
if (ps2_mouse_timer)
|
||||
{
|
||||
gptimer_stop(ps2_mouse_timer);
|
||||
gptimer_disable(ps2_mouse_timer);
|
||||
gptimer_del_timer(ps2_mouse_timer);
|
||||
ps2_mouse_timer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool ps2_mouse_is_active()
|
||||
{
|
||||
return ps2_mouse_active;
|
||||
}
|
||||
|
||||
esp_err_t ps2_mouse_send_movement(int dx, int dy, unsigned buttons)
|
||||
{
|
||||
if (!ps2_mouse_active)
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
|
||||
portENTER_CRITICAL(&ps2_mouse_lock);
|
||||
|
||||
if (!ps2_mouse_streaming || ps2_mouse_remote_mode)
|
||||
{
|
||||
portEXIT_CRITICAL(&ps2_mouse_lock);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
ps2_mouse_dx_accum += dx;
|
||||
ps2_mouse_dy_accum += dy;
|
||||
ps2_mouse_buttons = (uint8_t)(buttons & 0x07);
|
||||
ps2_mouse_motion_pending = true;
|
||||
|
||||
ps2_mouse_queue_motion_if_possible_isr();
|
||||
ps2_mouse_try_start_tx_isr();
|
||||
|
||||
portEXIT_CRITICAL(&ps2_mouse_lock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ps2_mouse_start()
|
||||
{
|
||||
esp_err_t err;
|
||||
|
||||
if (ps2_mouse_active)
|
||||
return ESP_OK;
|
||||
|
||||
portENTER_CRITICAL(&ps2_mouse_lock);
|
||||
ps2_mouse_active = true;
|
||||
ps2_mouse_reset_protocol_state_isr();
|
||||
portEXIT_CRITICAL(&ps2_mouse_lock);
|
||||
|
||||
err = ps2_mouse_init();
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
portENTER_CRITICAL(&ps2_mouse_lock);
|
||||
ps2_mouse_active = false;
|
||||
portEXIT_CRITICAL(&ps2_mouse_lock);
|
||||
ps2_mouse_deinit();
|
||||
return err;
|
||||
}
|
||||
|
||||
portENTER_CRITICAL(&ps2_mouse_lock);
|
||||
ps2_mouse_streaming = true;
|
||||
ps2_mouse_remote_mode = false;
|
||||
ps2_mouse_expect_param = false;
|
||||
ps2_mouse_pending_cmd = 0;
|
||||
portEXIT_CRITICAL(&ps2_mouse_lock);
|
||||
|
||||
ESP_LOGI("ps2_mouse", "ps/2 mouse started on GPIO%u=data, GPIO%u=clk, streaming enabled", PS2_MOUSE_GPIO_DATA, PS2_MOUSE_GPIO_CLK);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
esp_err_t ps2_mouse_start();
|
||||
esp_err_t ps2_mouse_send_movement(int dx, int dy, unsigned buttons);
|
||||
bool ps2_mouse_is_active();
|
||||
+1207
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/sdmmc_host.h"
|
||||
#include "sdmmc_cmd.h"
|
||||
#include "sdmmc.h"
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32P4)
|
||||
#define SD_SLOT SDMMC_HOST_SLOT_1
|
||||
#define SD_CLK GPIO_NUM_43
|
||||
#define SD_CMD GPIO_NUM_44
|
||||
#define SD_D0 GPIO_NUM_39
|
||||
#define SD_D1 GPIO_NUM_40
|
||||
#define SD_D2 GPIO_NUM_41
|
||||
#define SD_D3 GPIO_NUM_42
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#define SD_SLOT SDMMC_HOST_SLOT_1
|
||||
#define SD_CLK GPIO_NUM_40
|
||||
#define SD_CMD GPIO_NUM_39
|
||||
#define SD_D0 GPIO_NUM_38
|
||||
#define SD_D1 GPIO_NUM_2
|
||||
#define SD_D2 GPIO_NUM_41
|
||||
#define SD_D3 GPIO_NUM_42
|
||||
#endif
|
||||
|
||||
#ifndef SDMMC_CMD13_SEND_STATUS
|
||||
#define SDMMC_CMD13_SEND_STATUS 13U
|
||||
#endif
|
||||
|
||||
#ifndef SDMMC_ARG_RCA
|
||||
#define SDMMC_ARG_RCA(rca_) ((uint32_t)(rca_) << 16)
|
||||
#endif
|
||||
|
||||
#ifndef SDMMC_R1
|
||||
#define SDMMC_R1(resp_) ((uint32_t)((resp_)[0]))
|
||||
#endif
|
||||
|
||||
#ifndef SDMMC_CMD7_SELECT_CARD
|
||||
#define SDMMC_CMD7_SELECT_CARD 7U
|
||||
#endif
|
||||
|
||||
#define SD_ST_OUT_OF_RANGE (1U << 31)
|
||||
#define SD_ST_ADDRESS_ERROR (1U << 30)
|
||||
#define SD_ST_BLOCK_LEN_ERROR (1U << 29)
|
||||
#define SD_ST_ERASE_SEQ_ERROR (1U << 28)
|
||||
#define SD_ST_ERASE_PARAM (1U << 27)
|
||||
#define SD_ST_WP_VIOLATION (1U << 26)
|
||||
#define SD_ST_CARD_IS_LOCKED (1U << 25)
|
||||
#define SD_ST_LOCK_UNLOCK_FAILED (1U << 24)
|
||||
#define SD_ST_COM_CRC_ERROR (1U << 23)
|
||||
#define SD_ST_ILLEGAL_COMMAND (1U << 22)
|
||||
#define SD_ST_CARD_ECC_FAILED (1U << 21)
|
||||
#define SD_ST_CC_ERROR (1U << 20)
|
||||
#define SD_ST_ERROR (1U << 19)
|
||||
#define SD_ST_CSD_OVERWRITE (1U << 16)
|
||||
#define SD_ST_WP_ERASE_SKIP (1U << 15)
|
||||
#define SD_ST_ERASE_RESET (1U << 13)
|
||||
|
||||
extern sdmmc_host_t sd_host;
|
||||
extern sdmmc_slot_config_t sd_slot;
|
||||
extern sdmmc_card_t sd_card;
|
||||
extern sdmmc_card_t *sd_card_ptr;
|
||||
extern bool sd_initialized;
|
||||
extern bool sd_fs_mounted;
|
||||
extern char sd_fs_base_path[32];
|
||||
extern sdmmc_card_t *sd_fs_card;
|
||||
|
||||
esp_err_t sd_init();
|
||||
esp_err_t sd_reinit();
|
||||
esp_err_t sd_ensure_ready();
|
||||
esp_err_t sd_probe_card();
|
||||
esp_err_t sd_fs_sense(const char *base_path);
|
||||
esp_log_level_t sd_host_log_suppress_begin();
|
||||
void sd_host_log_suppress_end(esp_log_level_t old_sd_host_level);
|
||||
void sd_fs_unmount_force();
|
||||
void sd_deinit();
|
||||
esp_err_t sd_read_sectors(uint32_t sec, uint32_t num);
|
||||
esp_err_t sd_card_erase();
|
||||
esp_err_t sd_fs_mount(const char *base_path, sdmmc_card_t **out_card);
|
||||
esp_err_t sd_fs_mount_quiet(const char *base_path, sdmmc_card_t **out_card);
|
||||
void sd_fs_unmount(const char *base_path, sdmmc_card_t *card);
|
||||
esp_err_t sd_fs_list_dir(const char *base_path, const char *path);
|
||||
esp_err_t sd_fs_read_file(const char *base_path, const char *path, void *dst, size_t dst_size, size_t *out_size);
|
||||
int sd_fs_build_full_path(const char *base_path, const char *path, char *full, size_t full_size);
|
||||
bool sd_has_retryable_errno(int err);
|
||||
bool sd_error_needs_reinit(esp_err_t err);
|
||||
void sd_log_cid(const sdmmc_cid_t *cid);
|
||||
void sd_log_ocr(const sdmmc_card_t *card);
|
||||
void sd_log_scr(const sdmmc_card_t *card);
|
||||
|
||||
void sdmmc_console_register_system_commands();
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32P4)
|
||||
void sd_ldo_init();
|
||||
#endif
|
||||
+1400
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "main.h"
|
||||
|
||||
#define SFX_CHANNELS 32
|
||||
#define SFX_VOLUME_MAX 255
|
||||
#define SFX_PAN_CENTER 128
|
||||
#define SFX_PITCH_ONE 0x0100
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const u8 *data;
|
||||
u32 data_size;
|
||||
u32 frame_count;
|
||||
u32 sample_rate;
|
||||
u16 block_align;
|
||||
u16 bits_per_sample;
|
||||
u8 channels;
|
||||
u8 is_signed;
|
||||
} SFX_WAV_INFO;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u8 active;
|
||||
u8 handle;
|
||||
u8 group;
|
||||
u8 group_order;
|
||||
u8 volume;
|
||||
u8 pan;
|
||||
u16 pitch;
|
||||
float position;
|
||||
SFX_WAV_INFO wav;
|
||||
} SFX_CHANNEL;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u8 active;
|
||||
u8 handle;
|
||||
u8 group;
|
||||
u8 group_order;
|
||||
u8 volume;
|
||||
u8 pan;
|
||||
u16 pitch;
|
||||
u32 sample_rate;
|
||||
u32 frame_count;
|
||||
u32 position;
|
||||
u16 bits_per_sample;
|
||||
u8 channels;
|
||||
u8 is_signed;
|
||||
} SFX_CHANNEL_STATE;
|
||||
|
||||
extern SFX_CHANNEL sfx_channels[SFX_CHANNELS];
|
||||
extern u8 sfx_global_volume;
|
||||
|
||||
void sfx_init();
|
||||
esp_err_t sfx_parse_wav_object(int handle, SFX_WAV_INFO *info);
|
||||
esp_err_t sfx_load_file(const char *path, int *out_handle, SFX_WAV_INFO *out_info);
|
||||
esp_err_t sfx_play(int handle, u8 group, u8 volume, u8 pan, u16 pitch, int *out_channel);
|
||||
esp_err_t sfx_stop(int channel);
|
||||
esp_err_t sfx_stop_group(u8 group);
|
||||
esp_err_t sfx_stop_handle(int handle);
|
||||
esp_err_t sfx_set_params(int channel, u8 volume, u8 pan, u16 pitch);
|
||||
esp_err_t sfx_set_volume(u8 volume);
|
||||
esp_err_t sfx_get_state(int channel, SFX_CHANNEL_STATE *state);
|
||||
esp_err_t sfx_get_state_snapshot(int channel, SFX_CHANNEL_STATE *state);
|
||||
esp_err_t sfx_play_sync(int handle, u8 group, u8 volume, u8 pan, u16 pitch, int *out_channel);
|
||||
esp_err_t sfx_stop_sync(int channel);
|
||||
esp_err_t sfx_stop_group_sync(u8 group);
|
||||
esp_err_t sfx_stop_handle_sync(int handle);
|
||||
esp_err_t sfx_set_params_sync(int channel, u8 volume, u8 pan, u16 pitch);
|
||||
esp_err_t sfx_set_volume_sync(u8 volume);
|
||||
esp_err_t sfx_get_state_sync(int channel, SFX_CHANNEL_STATE *state);
|
||||
void sfx_render(float *mix, int sample_count, int output_sample_rate);
|
||||
void sfx_console_register_system_commands();
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
|
||||
#define DMA_BUF_SIZE 16384
|
||||
|
||||
enum
|
||||
{
|
||||
DREQ_WSCAN,
|
||||
DREQ_DATA,
|
||||
DREQ_ZIP,
|
||||
DREQ_RND,
|
||||
DREQ_URL,
|
||||
DREQ_STREAM,
|
||||
DREQ_TRACK_STREAM,
|
||||
DREQ_VFS_REQUEST,
|
||||
DREQ_VFS_RESULT,
|
||||
};
|
||||
|
||||
u8 rd_reg8(u8 reg);
|
||||
u32 rd_reg32(u8 reg);
|
||||
void rd_regs(u8 reg, const void *data, int size);
|
||||
void wr_reg8(u8 reg, u8 val);
|
||||
void wr_reg32(u8 reg, u32 val);
|
||||
void wr_regs(u8 reg, const void *data, int size);
|
||||
|
||||
void init_spi_configs();
|
||||
void init_slave_hd();
|
||||
esp_err_t spi_switch_to_master();
|
||||
esp_err_t spi_switch_to_slave();
|
||||
void spi_master_set_clock_hz(u32 hz);
|
||||
u32 spi_master_get_actual_freq_hz();
|
||||
esp_err_t spi_master_set_data_lines(u8 lines);
|
||||
esp_err_t spi_master_write_buf(u8 cmd, u16 addr, const void *tx_data, size_t size);
|
||||
esp_err_t spi_master_read_buf(u8 cmd, u16 addr, void *rx_data, size_t size);
|
||||
esp_err_t spi_master_xfer(void *tx_data, void *rx_data, size_t size);
|
||||
esp_err_t spi_master_write_inline(u8 cmd, u16 addr, u32 value, size_t size);
|
||||
esp_err_t spi_master_read_inline(u8 cmd, u16 addr, u32 *value, size_t size);
|
||||
esp_err_t spi_master_host_cmd_inline(u8 cmd, u8 param);
|
||||
bool spi_master_bg_is_busy();
|
||||
bool spi_master_bg_is_done();
|
||||
esp_err_t spi_master_bg_get_result();
|
||||
esp_err_t spi_master_bg_wait_done(TickType_t ticks_to_wait);
|
||||
esp_err_t spi_master_write_buf_bg(u8 cmd, u16 addr, const void *tx_data, size_t size);
|
||||
esp_err_t spi_master_read_buf_bg(u8 cmd, u16 addr, void *rx_data, size_t size);
|
||||
void spi_master_bg_task(void *arg);
|
||||
|
||||
void command();
|
||||
void sender_task(void *arg);
|
||||
void receiver_task(void *arg);
|
||||
|
||||
void put_rxq(int type);
|
||||
void put_txq(int type);
|
||||
u32 prepare_tx_data(u8 type, size_t size);
|
||||
void process_rx_data(u8 type, size_t size);
|
||||
void set_status(u8 err);
|
||||
u8* get_dma_buf(void);
|
||||
void spi_slave_dma_send_direct(size_t len);
|
||||
void wait_status(u8 status);
|
||||
void wait_not_status(u8 status);
|
||||
@@ -0,0 +1,80 @@
|
||||
|
||||
#include <limits.h>
|
||||
#include "esp_timer.h"
|
||||
#include "stats.h"
|
||||
|
||||
namespace stats
|
||||
{
|
||||
STATS_t _st = {};
|
||||
int64_t start_t;
|
||||
u32 last_cmd_exec_time;
|
||||
|
||||
void reset_audio_render()
|
||||
{
|
||||
_st.audio_tracker_last_us = 0;
|
||||
_st.audio_tracker_min_us = INT_MAX;
|
||||
_st.audio_tracker_max_us = 0;
|
||||
_st.audio_tracker_last_cpu_x10 = 0;
|
||||
_st.audio_tracker_min_cpu_x10 = INT_MAX;
|
||||
_st.audio_tracker_max_cpu_x10 = 0;
|
||||
|
||||
_st.audio_opl_last_us = 0;
|
||||
_st.audio_opl_min_us = INT_MAX;
|
||||
_st.audio_opl_max_us = 0;
|
||||
_st.audio_opl_last_cpu_x10 = 0;
|
||||
_st.audio_opl_min_cpu_x10 = INT_MAX;
|
||||
_st.audio_opl_max_cpu_x10 = 0;
|
||||
|
||||
_st.audio_sfx_last_us = 0;
|
||||
_st.audio_sfx_min_us = INT_MAX;
|
||||
_st.audio_sfx_max_us = 0;
|
||||
_st.audio_sfx_last_cpu_x10 = 0;
|
||||
_st.audio_sfx_min_cpu_x10 = INT_MAX;
|
||||
_st.audio_sfx_max_cpu_x10 = 0;
|
||||
|
||||
_st.audio_total_last_us = 0;
|
||||
_st.audio_total_min_us = INT_MAX;
|
||||
_st.audio_total_max_us = 0;
|
||||
_st.audio_total_last_cpu_x10 = 0;
|
||||
_st.audio_total_min_cpu_x10 = INT_MAX;
|
||||
_st.audio_total_max_cpu_x10 = 0;
|
||||
}
|
||||
|
||||
void init()
|
||||
{
|
||||
_st.drq_data_start_min_us = INT_MAX;
|
||||
_st.drq_data_end_min_us = INT_MAX;
|
||||
reset_audio_render();
|
||||
}
|
||||
|
||||
void update_audio_render(int *last_us, int *min_us, int *max_us, int *last_cpu_x10, int *min_cpu_x10, int *max_cpu_x10, int us, int sample_rate, int sample_count)
|
||||
{
|
||||
int cpu_x10 = 0;
|
||||
|
||||
if (sample_count > 0)
|
||||
cpu_x10 = (int)((int64_t)us * sample_rate * 10 / sample_count / 10000);
|
||||
|
||||
*last_us = us;
|
||||
if (us < *min_us) *min_us = us;
|
||||
if (us > *max_us) *max_us = us;
|
||||
|
||||
*last_cpu_x10 = cpu_x10;
|
||||
if (cpu_x10 < *min_cpu_x10) *min_cpu_x10 = cpu_x10;
|
||||
if (cpu_x10 > *max_cpu_x10) *max_cpu_x10 = cpu_x10;
|
||||
}
|
||||
|
||||
void set_start()
|
||||
{
|
||||
start_t = esp_timer_get_time();
|
||||
}
|
||||
|
||||
void set_end()
|
||||
{
|
||||
last_cmd_exec_time = esp_timer_get_time() - start_t;
|
||||
}
|
||||
|
||||
u32 get_time()
|
||||
{
|
||||
return last_cmd_exec_time;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "main.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int drq_data_t;
|
||||
int drq_data_start_last_us;
|
||||
int drq_data_start_min_us;
|
||||
int drq_data_start_max_us;
|
||||
int drq_data_end_last_us;
|
||||
int drq_data_end_min_us;
|
||||
int drq_data_end_max_us;
|
||||
|
||||
int audio_tracker_last_us;
|
||||
int audio_tracker_min_us;
|
||||
int audio_tracker_max_us;
|
||||
int audio_tracker_last_cpu_x10;
|
||||
int audio_tracker_min_cpu_x10;
|
||||
int audio_tracker_max_cpu_x10;
|
||||
|
||||
int audio_opl_last_us;
|
||||
int audio_opl_min_us;
|
||||
int audio_opl_max_us;
|
||||
int audio_opl_last_cpu_x10;
|
||||
int audio_opl_min_cpu_x10;
|
||||
int audio_opl_max_cpu_x10;
|
||||
|
||||
int audio_sfx_last_us;
|
||||
int audio_sfx_min_us;
|
||||
int audio_sfx_max_us;
|
||||
int audio_sfx_last_cpu_x10;
|
||||
int audio_sfx_min_cpu_x10;
|
||||
int audio_sfx_max_cpu_x10;
|
||||
|
||||
int audio_total_last_us;
|
||||
int audio_total_min_us;
|
||||
int audio_total_max_us;
|
||||
int audio_total_last_cpu_x10;
|
||||
int audio_total_min_cpu_x10;
|
||||
int audio_total_max_cpu_x10;
|
||||
|
||||
float xm_samp_min;
|
||||
float xm_samp_max;
|
||||
|
||||
char runtime_stats_buffer[2048];
|
||||
} STATS_t;
|
||||
|
||||
namespace stats
|
||||
{
|
||||
extern STATS_t _st;
|
||||
|
||||
void init();
|
||||
void reset_audio_render();
|
||||
void update_audio_render(int *last_us, int *min_us, int *max_us, int *last_cpu_x10, int *min_cpu_x10, int *max_cpu_x10, int us, int sample_rate, int sample_count);
|
||||
void set_start();
|
||||
void set_end();
|
||||
u32 get_time();
|
||||
};
|
||||
@@ -0,0 +1,502 @@
|
||||
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "esp_crt_bundle.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "esp_spi_defs.h"
|
||||
#include "spi_slave.h"
|
||||
#include "helper.h"
|
||||
#include "stream_client.h"
|
||||
|
||||
static const char * TAG = "stream";
|
||||
|
||||
#define RING_BUF_SIZE (64 * 1024)
|
||||
|
||||
static uint8_t * ring_buf = NULL;
|
||||
static volatile uint32_t ring_head = 0;
|
||||
static volatile uint32_t ring_tail = 0;
|
||||
static volatile bool ring_eof = false;
|
||||
static SemaphoreHandle_t ring_mutex = NULL;
|
||||
|
||||
static TaskHandle_t reader_task_handle = NULL;
|
||||
static int stream_sock = -1;
|
||||
static esp_http_client_handle_t stream_http = NULL;
|
||||
static uint32_t stream_total_size = 0;
|
||||
static uint32_t stream_read_size = 0;
|
||||
static bool stream_is_gopher = false;
|
||||
|
||||
static void reader_task(void *arg);
|
||||
|
||||
static uint32_t ring_available(void)
|
||||
{
|
||||
uint32_t h = ring_head;
|
||||
uint32_t t = ring_tail;
|
||||
if (h >= t) return h - t;
|
||||
return RING_BUF_SIZE - t + h;
|
||||
}
|
||||
|
||||
static uint32_t ring_free(void)
|
||||
{
|
||||
return RING_BUF_SIZE - ring_available() - 1;
|
||||
}
|
||||
|
||||
static esp_err_t stream_ring_alloc(void)
|
||||
{
|
||||
if (ring_buf) return ESP_OK;
|
||||
|
||||
ring_buf = (uint8_t*)heap_caps_malloc(RING_BUF_SIZE, MALLOC_CAP_SPIRAM);
|
||||
if (!ring_buf)
|
||||
{
|
||||
ESP_LOGE(TAG, "ring buffer alloc failed");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "ring buffer allocated: %p", ring_buf);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void stream_ring_free(void)
|
||||
{
|
||||
if (!ring_buf) return;
|
||||
|
||||
heap_caps_free(ring_buf);
|
||||
ring_buf = NULL;
|
||||
ESP_LOGI(TAG, "ring buffer freed");
|
||||
}
|
||||
|
||||
static void stream_ring_reset(void)
|
||||
{
|
||||
xSemaphoreTake(ring_mutex, portMAX_DELAY);
|
||||
ring_head = 0;
|
||||
ring_tail = 0;
|
||||
ring_eof = false;
|
||||
xSemaphoreGive(ring_mutex);
|
||||
}
|
||||
|
||||
static void stream_transport_close(void)
|
||||
{
|
||||
if (stream_http)
|
||||
{
|
||||
esp_http_client_close(stream_http);
|
||||
esp_http_client_cleanup(stream_http);
|
||||
stream_http = NULL;
|
||||
}
|
||||
|
||||
if (stream_sock >= 0)
|
||||
{
|
||||
close(stream_sock);
|
||||
stream_sock = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t stream_reader_start(void)
|
||||
{
|
||||
esp_err_t err = stream_ring_alloc();
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
stream_ring_reset();
|
||||
stream_read_size = 0;
|
||||
|
||||
if (xTaskCreatePinnedToCoreWithCaps(reader_task, "stream_reader", 4096, NULL, STREAMER_TASK_PRIO, &reader_task_handle, 0, task_ram_type_non_critical) == pdPASS)
|
||||
return ESP_OK;
|
||||
|
||||
stream_ring_free();
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static void stream_state_reset(void)
|
||||
{
|
||||
stream_total_size = 0;
|
||||
stream_read_size = 0;
|
||||
stream_is_gopher = false;
|
||||
stream_ring_reset();
|
||||
}
|
||||
|
||||
static void reader_task(void *arg)
|
||||
{
|
||||
uint8_t temp_buf[512];
|
||||
|
||||
while (true)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (stream_http)
|
||||
{
|
||||
len = esp_http_client_read(stream_http, (char*)temp_buf, sizeof(temp_buf));
|
||||
}
|
||||
else if (stream_sock >= 0)
|
||||
{
|
||||
len = read(stream_sock, temp_buf, sizeof(temp_buf));
|
||||
}
|
||||
|
||||
if (len <= 0)
|
||||
{
|
||||
xSemaphoreTake(ring_mutex, portMAX_DELAY);
|
||||
ring_eof = true;
|
||||
xSemaphoreGive(ring_mutex);
|
||||
ESP_LOGI(TAG, "reader: EOF, total=%u", stream_read_size);
|
||||
break;
|
||||
}
|
||||
|
||||
stream_read_size += len;
|
||||
|
||||
// Wait for space in ring buffer
|
||||
while (true)
|
||||
{
|
||||
xSemaphoreTake(ring_mutex, portMAX_DELAY);
|
||||
uint32_t free = ring_free();
|
||||
xSemaphoreGive(ring_mutex);
|
||||
|
||||
if (free >= (uint32_t) len) break;
|
||||
vTaskDelay(1);
|
||||
}
|
||||
|
||||
// Write to ring buffer
|
||||
xSemaphoreTake(ring_mutex, portMAX_DELAY);
|
||||
uint32_t h = ring_head;
|
||||
for(int i = 0; i < len; i++)
|
||||
{
|
||||
ring_buf[h] = temp_buf[i];
|
||||
h = (h + 1) % RING_BUF_SIZE;
|
||||
}
|
||||
ring_head = h;
|
||||
xSemaphoreGive(ring_mutex);
|
||||
}
|
||||
|
||||
reader_task_handle = NULL;
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void stream_init(void)
|
||||
{
|
||||
if (!ring_mutex)
|
||||
ring_mutex = xSemaphoreCreateMutexWithCaps(task_ram_type_non_critical);
|
||||
|
||||
stream_sock = -1;
|
||||
stream_http = NULL;
|
||||
reader_task_handle = NULL;
|
||||
stream_state_reset();
|
||||
|
||||
ESP_LOGI(TAG, "init done");
|
||||
}
|
||||
|
||||
static void stream_http_common(bool use_https)
|
||||
{
|
||||
ESP_LOGI(TAG, "STREAM_%s_START", use_https ? "HTTPS" : "HTTP");
|
||||
|
||||
stream_state_reset();
|
||||
|
||||
if (!net.url[0])
|
||||
{
|
||||
ESP_LOGW(TAG, "URL not set");
|
||||
wr_reg8(ESP_REG_STATUS, 0x81);
|
||||
return;
|
||||
}
|
||||
|
||||
char * url = (char*)net.url;
|
||||
const char * prefix = use_https ? "https://" : "http://";
|
||||
int prefix_len = use_https ? 8 : 7;
|
||||
|
||||
if (strncmp(url, prefix, prefix_len) != 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "invalid URL format");
|
||||
wr_reg8(ESP_REG_STATUS, 0x81);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "GET %s", url);
|
||||
|
||||
esp_http_client_config_t cfg = {};
|
||||
cfg.url = url;
|
||||
cfg.timeout_ms = 30000;
|
||||
cfg.buffer_size = 512;
|
||||
cfg.user_agent = "ZiFi/1.0";
|
||||
|
||||
if (use_https)
|
||||
cfg.crt_bundle_attach = esp_crt_bundle_attach;
|
||||
|
||||
stream_http = esp_http_client_init( & cfg);
|
||||
|
||||
if (!stream_http)
|
||||
{
|
||||
ESP_LOGW(TAG, "init fail");
|
||||
wr_reg8(ESP_REG_STATUS, 0xB2);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t err = esp_http_client_open(stream_http, 0);
|
||||
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "open fail");
|
||||
esp_http_client_cleanup(stream_http);
|
||||
stream_http = NULL;
|
||||
wr_reg8(ESP_REG_STATUS, 0xB2);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_http_client_fetch_headers(stream_http);
|
||||
int code = esp_http_client_get_status_code(stream_http);
|
||||
ESP_LOGI(TAG, "status %d", code);
|
||||
|
||||
if (code < 200 || code >= 300)
|
||||
{
|
||||
esp_http_client_close(stream_http);
|
||||
esp_http_client_cleanup(stream_http);
|
||||
stream_http = NULL;
|
||||
wr_reg8(ESP_REG_STATUS, 0xB3);
|
||||
return;
|
||||
}
|
||||
|
||||
int content_length = esp_http_client_get_content_length(stream_http);
|
||||
if (content_length < 0)
|
||||
stream_total_size = 0xFFFFFFFF;
|
||||
else
|
||||
stream_total_size = (uint32_t) content_length;
|
||||
|
||||
ESP_LOGI(TAG, "connected, size=%u", stream_total_size);
|
||||
|
||||
if (stream_reader_start() != ESP_OK)
|
||||
{
|
||||
stream_transport_close();
|
||||
wr_reg8(ESP_REG_STATUS, 0xB0);
|
||||
return;
|
||||
}
|
||||
|
||||
wr_reg32(ESP_REG_DATA_SIZE, stream_total_size);
|
||||
wr_reg8(ESP_REG_STATUS, ESP_ST_READY);
|
||||
}
|
||||
|
||||
void stream_http_start(void)
|
||||
{
|
||||
stream_http_common(false);
|
||||
}
|
||||
|
||||
void stream_https_start(void)
|
||||
{
|
||||
stream_http_common(true);
|
||||
}
|
||||
|
||||
void stream_gopher_start(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "STREAM_GOPHER_START");
|
||||
|
||||
stream_state_reset();
|
||||
stream_is_gopher = true;
|
||||
|
||||
if (!net.url[0])
|
||||
{
|
||||
ESP_LOGW(TAG, "URL not set");
|
||||
wr_reg8(ESP_REG_STATUS, 0x81);
|
||||
return;
|
||||
}
|
||||
|
||||
char * url = (char*)net.url;
|
||||
|
||||
if (strncmp(url, "gopher://", 9) != 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "invalid URL format");
|
||||
wr_reg8(ESP_REG_STATUS, 0x81);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "GET %s", url);
|
||||
|
||||
char host[256];
|
||||
int port = 70;
|
||||
char selector[256];
|
||||
|
||||
memset(host, 0, sizeof(host));
|
||||
memset(selector, 0, sizeof(selector));
|
||||
//strcpy(selector, "/");
|
||||
selector[0] = '\0';
|
||||
|
||||
const char * p = url + 9;
|
||||
const char * slash = strchr(p, '/');
|
||||
const char * colon = strchr(p, ':');
|
||||
|
||||
if (colon && (!slash || colon < slash))
|
||||
{
|
||||
int host_len = colon - p;
|
||||
strncpy(host, p, host_len);
|
||||
host[host_len] = 0;
|
||||
port = atoi(colon + 1);
|
||||
|
||||
if (slash)
|
||||
{
|
||||
const char * sel_start = slash + 1;
|
||||
// Skip Gopher type character and slash if present (e.g., "9/" -> "")
|
||||
if (sel_start[0] && sel_start[1] == '/' && strchr("0123456789+gIihs", sel_start[0]))
|
||||
sel_start += 2;
|
||||
|
||||
strncpy(selector, sel_start, sizeof(selector) - 1);
|
||||
}
|
||||
}
|
||||
else if (slash)
|
||||
{
|
||||
int host_len = slash - p;
|
||||
strncpy(host, p, host_len);
|
||||
host[host_len] = 0;
|
||||
const char * sel_start = slash + 1;
|
||||
|
||||
// Skip Gopher type character and slash if present
|
||||
if (sel_start[0] && sel_start[1] == '/' && strchr("0123456789+gIihs", sel_start[0]))
|
||||
sel_start += 2;
|
||||
|
||||
strncpy(selector, sel_start, sizeof(selector) - 1);
|
||||
}
|
||||
else
|
||||
strncpy(host, p, sizeof(host) - 1);
|
||||
|
||||
struct hostent * server = gethostbyname(host);
|
||||
|
||||
if (!server)
|
||||
{
|
||||
ESP_LOGW(TAG, "DNS lookup failed");
|
||||
wr_reg8(ESP_REG_STATUS, 0xB1);
|
||||
return;
|
||||
}
|
||||
|
||||
stream_sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
if (stream_sock < 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "socket creation failed");
|
||||
wr_reg8(ESP_REG_STATUS, 0xB2);
|
||||
return;
|
||||
}
|
||||
|
||||
struct sockaddr_in serv_addr;
|
||||
memset(&serv_addr, 0, sizeof(serv_addr));
|
||||
serv_addr.sin_family = AF_INET;
|
||||
serv_addr.sin_port = htons(port);
|
||||
memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length);
|
||||
|
||||
if (connect(stream_sock, (struct sockaddr*)& serv_addr, sizeof(serv_addr)) < 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "connection failed");
|
||||
close(stream_sock);
|
||||
stream_sock = -1;
|
||||
wr_reg8(ESP_REG_STATUS, 0xB2);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set socket timeout to 30 seconds
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = 30;
|
||||
timeout.tv_usec = 0;
|
||||
setsockopt(stream_sock, SOL_SOCKET, SO_RCVTIMEO, & timeout, sizeof(timeout));
|
||||
|
||||
char request[512];
|
||||
snprintf(request, sizeof(request), "/%s\r\n", selector);
|
||||
write(stream_sock, request, strlen(request));
|
||||
|
||||
ESP_LOGI(TAG, "sent selector: [%s]", selector);
|
||||
|
||||
// Size unknown from Gopher
|
||||
stream_total_size = 0xFFFFFFFF;
|
||||
|
||||
ESP_LOGI(TAG, "connected");
|
||||
|
||||
if (stream_reader_start() != ESP_OK)
|
||||
{
|
||||
stream_transport_close();
|
||||
wr_reg8(ESP_REG_STATUS, 0xB0);
|
||||
return;
|
||||
}
|
||||
|
||||
wr_reg32(ESP_REG_DATA_SIZE, stream_total_size);
|
||||
wr_reg8(ESP_REG_STATUS, ESP_ST_READY);
|
||||
}
|
||||
|
||||
void stream_read(void)
|
||||
{
|
||||
if (stream_sock < 0 && !stream_http)
|
||||
{
|
||||
ESP_LOGW(TAG, "stream not opened");
|
||||
wr_reg8(ESP_REG_STATUS, 0x82);
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for data in ring buffer or EOF
|
||||
int timeout = 0;
|
||||
|
||||
while (timeout < 30000)
|
||||
{
|
||||
xSemaphoreTake(ring_mutex, portMAX_DELAY);
|
||||
uint32_t avail = ring_available();
|
||||
bool eof = ring_eof;
|
||||
xSemaphoreGive(ring_mutex);
|
||||
|
||||
if (avail > 0 || eof) break;
|
||||
vTaskDelay(1);
|
||||
timeout++;
|
||||
}
|
||||
|
||||
xSemaphoreTake(ring_mutex, portMAX_DELAY);
|
||||
uint32_t avail = ring_available();
|
||||
bool eof = ring_eof;
|
||||
|
||||
if (avail == 0 && eof)
|
||||
{
|
||||
xSemaphoreGive(ring_mutex);
|
||||
ESP_LOGI(TAG, "stream end, total=%u", stream_read_size);
|
||||
wr_reg32(ESP_REG_DATA_SIZE, 0);
|
||||
wr_reg8(ESP_REG_STATUS, ESP_ST_READY);
|
||||
return;
|
||||
}
|
||||
|
||||
if (avail == 0)
|
||||
{
|
||||
xSemaphoreGive(ring_mutex);
|
||||
ESP_LOGW(TAG, "timeout waiting for data");
|
||||
wr_reg32(ESP_REG_DATA_SIZE, 0);
|
||||
wr_reg8(ESP_REG_STATUS, ESP_ST_READY);
|
||||
return;
|
||||
}
|
||||
|
||||
// Read up to 512 bytes from ring buffer
|
||||
uint32_t to_read = avail > 512 ? 512 : avail;
|
||||
uint8_t * dma = get_dma_buf();
|
||||
uint32_t t = ring_tail;
|
||||
|
||||
for (uint32_t i = 0; i < to_read; i++)
|
||||
{
|
||||
dma[i] = ring_buf[t];
|
||||
t = (t + 1) % RING_BUF_SIZE;
|
||||
}
|
||||
ring_tail = t;
|
||||
|
||||
xSemaphoreGive(ring_mutex);
|
||||
|
||||
spi_slave_dma_send_direct(to_read);
|
||||
}
|
||||
|
||||
void stream_close(void)
|
||||
{
|
||||
if (reader_task_handle)
|
||||
{
|
||||
vTaskDelete(reader_task_handle);
|
||||
reader_task_handle = NULL;
|
||||
}
|
||||
|
||||
stream_transport_close();
|
||||
|
||||
ESP_LOGI(TAG, "stream closed, total read=%u", stream_read_size);
|
||||
|
||||
stream_state_reset();
|
||||
stream_ring_free();
|
||||
|
||||
wr_reg8(ESP_REG_STATUS, ESP_ST_READY);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
void stream_init(void);
|
||||
void stream_http_start(void);
|
||||
void stream_https_start(void);
|
||||
void stream_gopher_start(void);
|
||||
void stream_read(void);
|
||||
void stream_close(void);
|
||||
+1917
File diff suppressed because it is too large
Load Diff
+13366
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,932 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_attr.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
// ============================================================================
|
||||
// Common tracker declarations
|
||||
// ============================================================================
|
||||
|
||||
// -------------------- Common tracker types --------------------
|
||||
|
||||
typedef enum tracker_format_e
|
||||
{
|
||||
TRACKER_FORMAT_XM = 0,
|
||||
TRACKER_FORMAT_MOD,
|
||||
TRACKER_FORMAT_S3M
|
||||
} tracker_format_t;
|
||||
|
||||
#define TRACKER_CONTEXT_SEGMENT_PAGE_CAPACITY 64
|
||||
#define TRACKER_CONTEXT_SEGMENTS_MAGIC 0x5453474dU
|
||||
#define TRACKER_CONTEXT_SEGMENTS_MAGIC_CHECK 0xaba7b8b2U
|
||||
|
||||
typedef enum tracker_context_segment_type_e
|
||||
{
|
||||
TRACKER_CONTEXT_SEG_CONTEXT = 0,
|
||||
TRACKER_CONTEXT_SEG_RUNTIME,
|
||||
TRACKER_CONTEXT_SEG_PATTERNS,
|
||||
TRACKER_CONTEXT_SEG_INSTRUMENTS,
|
||||
TRACKER_CONTEXT_SEG_SAMPLES,
|
||||
TRACKER_CONTEXT_SEG_FORMAT_EXTRA
|
||||
} tracker_context_segment_type_t;
|
||||
|
||||
typedef struct tracker_context_segment_s
|
||||
{
|
||||
void *addr;
|
||||
size_t size;
|
||||
uint8_t type;
|
||||
uint8_t flags;
|
||||
uint16_t index;
|
||||
} tracker_context_segment_t;
|
||||
|
||||
typedef struct tracker_context_segment_page_s tracker_context_segment_page_t;
|
||||
|
||||
// -------------------- Common byte order helpers --------------------
|
||||
|
||||
uint16_t tracker_rd_be16(const uint8_t *p);
|
||||
uint16_t tracker_rd_le16(const uint8_t *p);
|
||||
uint32_t tracker_rd_le24(const uint8_t *p);
|
||||
uint32_t tracker_rd_le32(const uint8_t *p);
|
||||
|
||||
// -------------------- Common size helpers --------------------
|
||||
|
||||
int tracker_size_add(size_t *dst, size_t add);
|
||||
int tracker_size_mul(size_t a, size_t b, size_t *out);
|
||||
|
||||
// -------------------- Common string/memory helpers --------------------
|
||||
|
||||
void tracker_copy_trimmed(char *dst, size_t dst_size, const uint8_t *src, size_t src_size);
|
||||
void tracker_memcpy_pad(void *dst, size_t dst_len, const void *src, size_t src_len, size_t offset);
|
||||
|
||||
// -------------------- XM format helpers --------------------
|
||||
|
||||
|
||||
// -------------------- XM grouped context allocation helpers --------------------
|
||||
|
||||
typedef struct xm_context_group_sizes_s
|
||||
{
|
||||
size_t context_size;
|
||||
size_t patterns_size;
|
||||
size_t instruments_size;
|
||||
size_t samples_size;
|
||||
size_t runtime_size;
|
||||
size_t format_extra_size;
|
||||
size_t total_size;
|
||||
} xm_context_group_sizes_t;
|
||||
|
||||
typedef struct xm_context_group_cursor_s
|
||||
{
|
||||
char *patterns;
|
||||
char *patterns_end;
|
||||
char *instruments;
|
||||
char *instruments_end;
|
||||
char *samples;
|
||||
char *samples_end;
|
||||
char *runtime;
|
||||
char *runtime_end;
|
||||
char *format_extra;
|
||||
char *format_extra_end;
|
||||
} xm_context_group_cursor_t;
|
||||
|
||||
// -------------------- MOD format helpers --------------------
|
||||
|
||||
// -------------------- S3M format helpers --------------------
|
||||
|
||||
// ============================================================================
|
||||
// XM public API
|
||||
// ============================================================================
|
||||
|
||||
/* Author: Romain "Artefact2" Dalmaso <artefact2@gmail.com> */
|
||||
/* Contributor: Dan Spencer <dan@atomicpotato.net> */
|
||||
|
||||
/* This program is free software. It comes without any warranty, to the
|
||||
* extent permitted by applicable law. You can redistribute it and/or
|
||||
* modify it under the terms of the Do What The Fuck You Want To Public
|
||||
* License, Version 2, as published by Sam Hocevar. See
|
||||
* http://sam.zoy.org/wtfpl/COPYING for more details. */
|
||||
|
||||
|
||||
|
||||
|
||||
#define XM_DEFENSIVE 1
|
||||
#define XM_LIBXMIZE_DELTA_SAMPLES 0
|
||||
#define XM_DEBUG 0
|
||||
#define XM_LINEAR_INTERPOLATION 1
|
||||
#define XM_RAMPING 1
|
||||
#define XM_STRINGS 1
|
||||
|
||||
struct xm_context_s;
|
||||
typedef struct xm_context_s xm_context_t;
|
||||
|
||||
typedef struct s3m_adlib_instrument_s
|
||||
{
|
||||
uint8_t type;
|
||||
uint8_t volume;
|
||||
uint32_t c4speed;
|
||||
uint8_t regs[12];
|
||||
} s3m_adlib_instrument_t;
|
||||
|
||||
typedef void* (*MFUNC)(size_t);
|
||||
|
||||
/** Create a XM context.
|
||||
*
|
||||
* @param moddata the contents of the module
|
||||
* @param moddata_length the length of the contents of the module, in bytes
|
||||
* @param rate play rate in Hz, recommended value of 48000
|
||||
*
|
||||
* @returns size on success
|
||||
* @returns -1 if module data is not sane
|
||||
* @returns -2 if memory allocation failed
|
||||
*/
|
||||
|
||||
int xm_create_context_safe(xm_context_t**, void* moddata, size_t moddata_length, uint32_t rate, MFUNC mfunc);
|
||||
|
||||
/** Create a XM context.
|
||||
*
|
||||
* This function will produce smaller code size compared to
|
||||
* xm_create_context(), but requires converting the .xm file to a
|
||||
* non-portable format beforehand.
|
||||
*
|
||||
* This function doesn't do any kind of error checking.
|
||||
*
|
||||
* @param libxmizeddata: pointer to module data, needs to point to a
|
||||
* writable area, freeing it is equivalent as calling
|
||||
* xm_free_context().
|
||||
*
|
||||
* @see xm_create_context()
|
||||
*/
|
||||
void xm_create_context_from_libxmize(xm_context_t**, char* libxmizeddata, uint32_t rate);
|
||||
|
||||
/** Free a XM context created by xm_create_context(). */
|
||||
void xm_free_context(xm_context_t*);
|
||||
|
||||
int tracker_context_register_segment(xm_context_t *ctx, void *addr, size_t size, uint8_t type);
|
||||
int tracker_context_has_registered_segments(const xm_context_t *ctx);
|
||||
uint16_t tracker_context_get_segment_count(const xm_context_t *ctx);
|
||||
int tracker_context_get_segment(const xm_context_t *ctx, uint16_t index, tracker_context_segment_t *segment);
|
||||
void tracker_context_free_segments(xm_context_t *ctx);
|
||||
|
||||
void xm_sample(xm_context_t* ctx, float* p_left, float* p_right);
|
||||
void xm_render(xm_context_t* ctx, float* out, size_t samples);
|
||||
|
||||
/** Set the maximum number of times a module can loop. After the
|
||||
* specified number of loops, calls to xm_generate_samples will only
|
||||
* generate silence. You can control the current number of loops with
|
||||
* xm_get_loop_count().
|
||||
*
|
||||
* @param loopcnt maximum number of loops. Use 0 to loop
|
||||
* indefinitely. */
|
||||
void xm_set_max_loop_count(xm_context_t*, uint8_t loopcnt);
|
||||
|
||||
/** Get the loop count of the currently playing module. This value is
|
||||
* 0 when the module is still playing, 1 when the module has looped
|
||||
* once, etc. */
|
||||
uint8_t xm_get_loop_count(xm_context_t*);
|
||||
|
||||
/** Get the length, in samples, of one full pass through the module loaded
|
||||
* into the given object handle (computed once at TRACK_INIT time). 0 if
|
||||
* the handle has no track info yet. */
|
||||
uint32_t xm_get_total_samples(int handle);
|
||||
|
||||
|
||||
|
||||
/** Seek to a specific position in a module.
|
||||
*
|
||||
* WARNING, WITH BIG LETTERS: seeking modules is broken by design,
|
||||
* don't expect miracles.
|
||||
*/
|
||||
void xm_seek(xm_context_t*, uint8_t pot, uint8_t row, uint16_t tick);
|
||||
|
||||
|
||||
|
||||
/** Mute or unmute a channel.
|
||||
*
|
||||
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
|
||||
*
|
||||
* @return whether the channel was muted.
|
||||
*/
|
||||
bool xm_mute_channel(xm_context_t*, uint16_t, bool);
|
||||
|
||||
/** Mute or unmute an instrument.
|
||||
*
|
||||
* @note Instrument numbers go from 1 to
|
||||
* xm_get_number_of_instruments(...).
|
||||
*
|
||||
* @return whether the instrument was muted.
|
||||
*/
|
||||
bool xm_mute_instrument(xm_context_t*, uint16_t, bool);
|
||||
|
||||
|
||||
|
||||
/** Get the module name as a NUL-terminated string. */
|
||||
const char* xm_get_module_name(xm_context_t*);
|
||||
|
||||
/** Get the tracker name as a NUL-terminated string. */
|
||||
const char* xm_get_tracker_name(xm_context_t*);
|
||||
|
||||
|
||||
|
||||
/** Get the number of channels. */
|
||||
uint16_t xm_get_number_of_channels(xm_context_t*);
|
||||
|
||||
/** Get the module length (in patterns). */
|
||||
uint16_t xm_get_module_length(xm_context_t*);
|
||||
|
||||
/** Get the number of patterns. */
|
||||
uint16_t xm_get_number_of_patterns(xm_context_t*);
|
||||
|
||||
/** Get the number of rows of a pattern.
|
||||
*
|
||||
* @note Pattern numbers go from 0 to
|
||||
* xm_get_number_of_patterns(...)-1.
|
||||
*/
|
||||
uint16_t xm_get_number_of_rows(xm_context_t*, uint16_t);
|
||||
|
||||
/** Get the number of instruments. */
|
||||
uint16_t xm_get_number_of_instruments(xm_context_t*);
|
||||
|
||||
/** Get the number of samples of an instrument.
|
||||
*
|
||||
* @note Instrument numbers go from 1 to
|
||||
* xm_get_number_of_instruments(...).
|
||||
*/
|
||||
uint16_t xm_get_number_of_samples(xm_context_t*, uint16_t);
|
||||
|
||||
/** Get the internal buffer for a given sample waveform.
|
||||
*
|
||||
* This buffer can be read from or written to, at any time, but the
|
||||
* length cannot change. The buffer must be cast to (int8_t*) or
|
||||
* (int16_t*) depending on the sample type.
|
||||
*
|
||||
* @note Instrument numbers go from 1 to
|
||||
* xm_get_number_of_instruments(...).
|
||||
*
|
||||
* @note Sample numbers go from 0 to
|
||||
* xm_get_nubmer_of_samples(...,instr)-1.
|
||||
*/
|
||||
void* xm_get_sample_waveform(xm_context_t*, uint16_t instr, uint16_t sample, size_t* length, uint8_t* bits);
|
||||
|
||||
|
||||
|
||||
/** Get the current module speed.
|
||||
*
|
||||
* @param bpm will receive the current BPM
|
||||
* @param tempo will receive the current tempo (ticks per line)
|
||||
*/
|
||||
void xm_get_playing_speed(xm_context_t*, uint16_t* bpm, uint16_t* tempo);
|
||||
|
||||
/** Get the current position in the module being played.
|
||||
*
|
||||
* @param pattern_index if not NULL, will receive the current pattern
|
||||
* index in the POT (pattern order table)
|
||||
*
|
||||
* @param pattern if not NULL, will receive the current pattern number
|
||||
*
|
||||
* @param row if not NULL, will receive the current row
|
||||
*
|
||||
* @param samples if not NULL, will receive the total number of
|
||||
* generated samples (divide by sample rate to get seconds of
|
||||
* generated audio)
|
||||
*/
|
||||
void xm_get_position(xm_context_t*, uint8_t* pattern_index, uint8_t* pattern, uint8_t* row, uint64_t* samples);
|
||||
|
||||
/** Get the latest time (in number of generated samples) when a
|
||||
* particular instrument was triggered in any channel.
|
||||
*
|
||||
* @note Instrument numbers go from 1 to
|
||||
* xm_get_number_of_instruments(...).
|
||||
*/
|
||||
uint64_t xm_get_latest_trigger_of_instrument(xm_context_t*, uint16_t);
|
||||
|
||||
/** Get the latest time (in number of generated samples) when a
|
||||
* particular sample was triggered in any channel.
|
||||
*
|
||||
* @note Instrument numbers go from 1 to
|
||||
* xm_get_number_of_instruments(...).
|
||||
*
|
||||
* @note Sample numbers go from 0 to
|
||||
* xm_get_nubmer_of_samples(...,instr)-1.
|
||||
*/
|
||||
uint64_t xm_get_latest_trigger_of_sample(xm_context_t*, uint16_t instr, uint16_t sample);
|
||||
|
||||
/** Get the latest time (in number of generated samples) when any
|
||||
* instrument was triggered in a given channel.
|
||||
*
|
||||
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
|
||||
*/
|
||||
uint64_t xm_get_latest_trigger_of_channel(xm_context_t*, uint16_t);
|
||||
|
||||
/** Checks whether a channel is active (ie: is playing something).
|
||||
*
|
||||
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
|
||||
*/
|
||||
bool xm_is_channel_active(xm_context_t*, uint16_t);
|
||||
|
||||
/** Get the instrument number currently playing in a channel.
|
||||
*
|
||||
* @returns instrument number, or 0 if channel is not active.
|
||||
*
|
||||
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
|
||||
*
|
||||
* @note Instrument numbers go from 1 to
|
||||
* xm_get_number_of_instruments(...).
|
||||
*/
|
||||
uint16_t xm_get_instrument_of_channel(xm_context_t*, uint16_t);
|
||||
|
||||
/** Get the frequency of the sample currently playing in a channel.
|
||||
*
|
||||
* @returns a frequency in Hz. If the channel is not active, return
|
||||
* value is undefined.
|
||||
*
|
||||
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
|
||||
*/
|
||||
float xm_get_frequency_of_channel(xm_context_t*, uint16_t);
|
||||
|
||||
/** Get the volume of the sample currently playing in a channel. This
|
||||
* takes into account envelopes, etc.
|
||||
*
|
||||
* @returns a volume between 0 or 1. If the channel is not active,
|
||||
* return value is undefined.
|
||||
*
|
||||
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
|
||||
*/
|
||||
float xm_get_volume_of_channel(xm_context_t*, uint16_t);
|
||||
|
||||
/** Get the panning of the sample currently playing in a channel. This
|
||||
* takes into account envelopes, etc.
|
||||
*
|
||||
* @returns a panning between 0 (L) and 1 (R). If the channel is not
|
||||
* active, return value is undefined.
|
||||
*
|
||||
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
|
||||
*/
|
||||
float xm_get_panning_of_channel(xm_context_t*, uint16_t);
|
||||
|
||||
// ============================================================================
|
||||
// Common tracker runtime internals
|
||||
// ============================================================================
|
||||
|
||||
/* Author: Romain "Artefact2" Dalmaso <artefact2@gmail.com> */
|
||||
|
||||
/* This program is free software. It comes without any warranty, to the
|
||||
* extent permitted by applicable law. You can redistribute it and/or
|
||||
* modify it under the terms of the Do What The Fuck You Want To Public
|
||||
* License, Version 2, as published by Sam Hocevar. See
|
||||
* http://sam.zoy.org/wtfpl/COPYING for more details. */
|
||||
|
||||
|
||||
#if XM_DEBUG
|
||||
#include <stdio.h>
|
||||
#define DEBUG(fmt, ...) \
|
||||
{ \
|
||||
fprintf(stderr, "%s(): " fmt "\n", __func__, __VA_ARGS__); \
|
||||
fflush(stderr); \
|
||||
}
|
||||
#else
|
||||
#define DEBUG(...)
|
||||
#endif
|
||||
|
||||
#if XM_BIG_ENDIAN
|
||||
#error "Big endian platforms are not yet supported, sorry"
|
||||
/* Make sure the compiler stops, even if #error is ignored */
|
||||
extern int __fail[-1];
|
||||
#endif
|
||||
|
||||
/* ----- XM constants ----- */
|
||||
|
||||
#define SAMPLE_NAME_LENGTH 22
|
||||
#define INSTRUMENT_NAME_LENGTH 22
|
||||
#define MODULE_NAME_LENGTH 20
|
||||
#define TRACKER_NAME_LENGTH 20
|
||||
#define PATTERN_ORDER_TABLE_LENGTH 256
|
||||
#define NUM_NOTES 96
|
||||
#define NUM_ENVELOPE_POINTS 12
|
||||
#define MAX_NUM_ROWS 256
|
||||
|
||||
#if XM_RAMPING
|
||||
#define XM_SAMPLE_RAMPING_POINTS 32
|
||||
#endif
|
||||
|
||||
/* ----- Data types ----- */
|
||||
|
||||
enum xm_waveform_type_e {
|
||||
XM_SINE_WAVEFORM = 0,
|
||||
XM_RAMP_DOWN_WAVEFORM = 1,
|
||||
XM_SQUARE_WAVEFORM = 2,
|
||||
XM_RANDOM_WAVEFORM = 3,
|
||||
XM_RAMP_UP_WAVEFORM = 4,
|
||||
};
|
||||
typedef enum xm_waveform_type_e xm_waveform_type_t;
|
||||
|
||||
enum xm_loop_type_e {
|
||||
XM_NO_LOOP,
|
||||
XM_FORWARD_LOOP,
|
||||
XM_PING_PONG_LOOP,
|
||||
};
|
||||
typedef enum xm_loop_type_e xm_loop_type_t;
|
||||
|
||||
enum xm_frequency_type_e {
|
||||
XM_LINEAR_FREQUENCIES,
|
||||
XM_AMIGA_FREQUENCIES,
|
||||
XM_S3M_FREQUENCIES,
|
||||
};
|
||||
|
||||
#define XM_EFFECT_S3M_SPEED 34
|
||||
#define XM_EFFECT_S3M_TEMPO 35
|
||||
#define XM_EFFECT_S3M_FINE_VIBRATO 36
|
||||
#define XM_EFFECT_S3M_CHANNEL_VOLUME 37
|
||||
#define XM_EFFECT_S3M_CHANNEL_VOLUME_SLIDE 38
|
||||
#define XM_EFFECT_S3M_POSITION_JUMP 39
|
||||
#define XM_EFFECT_S3M_PATTERN_BREAK 40
|
||||
#define XM_EFFECT_S3M_VOLUME_SLIDE 41
|
||||
#define XM_EFFECT_S3M_PORTAMENTO_DOWN 42
|
||||
#define XM_EFFECT_S3M_PORTAMENTO_UP 43
|
||||
#define XM_EFFECT_S3M_TONE_PORTAMENTO 44
|
||||
#define XM_EFFECT_S3M_VIBRATO 45
|
||||
#define XM_EFFECT_S3M_TREMOR 46
|
||||
#define XM_EFFECT_S3M_ARPEGGIO 47
|
||||
#define XM_EFFECT_S3M_VIBRATO_VOLUME_SLIDE 48
|
||||
#define XM_EFFECT_S3M_TONE_PORTAMENTO_VOLUME_SLIDE 49
|
||||
#define XM_EFFECT_S3M_SAMPLE_OFFSET 50
|
||||
#define XM_EFFECT_S3M_PANNING_SLIDE 51
|
||||
#define XM_EFFECT_S3M_RETRIG 52
|
||||
#define XM_EFFECT_S3M_TREMOLO 53
|
||||
#define XM_EFFECT_S3M_GLOBAL_VOLUME 54
|
||||
#define XM_EFFECT_S3M_GLOBAL_VOLUME_SLIDE 55
|
||||
#define XM_EFFECT_S3M_PANNING 56
|
||||
#define XM_EFFECT_S3M_EXTENDED 57
|
||||
#define XM_EFFECT_S3M_PANBRELLO 58
|
||||
#define XM_EFFECT_S3M_MIDI_MACRO 59
|
||||
#define XM_EFFECT_S3M_NONE 60
|
||||
typedef enum xm_frequency_type_e xm_frequency_type_t;
|
||||
|
||||
struct xm_envelope_point_s {
|
||||
uint16_t frame;
|
||||
uint16_t value;
|
||||
};
|
||||
typedef struct xm_envelope_point_s xm_envelope_point_t;
|
||||
|
||||
struct xm_envelope_s {
|
||||
xm_envelope_point_t points[NUM_ENVELOPE_POINTS];
|
||||
uint8_t num_points;
|
||||
uint8_t sustain_point;
|
||||
uint8_t loop_start_point;
|
||||
uint8_t loop_end_point;
|
||||
bool enabled;
|
||||
bool sustain_enabled;
|
||||
bool loop_enabled;
|
||||
};
|
||||
typedef struct xm_envelope_s xm_envelope_t;
|
||||
|
||||
struct xm_sample_s {
|
||||
#if XM_STRINGS
|
||||
char name[SAMPLE_NAME_LENGTH + 1];
|
||||
#endif
|
||||
uint8_t bits; /* Either 8 or 16 */
|
||||
|
||||
uint32_t length;
|
||||
uint32_t loop_start;
|
||||
uint32_t loop_length;
|
||||
uint32_t loop_end;
|
||||
float volume;
|
||||
int8_t finetune;
|
||||
xm_loop_type_t loop_type;
|
||||
float panning;
|
||||
int8_t relative_note;
|
||||
uint32_t c4speed; /* S3M C4Speed / OpenMPT c5speed field; used by S3M frequency mode. */
|
||||
uint64_t latest_trigger;
|
||||
|
||||
union {
|
||||
int8_t* data8;
|
||||
int16_t* data16;
|
||||
};
|
||||
};
|
||||
typedef struct xm_sample_s xm_sample_t;
|
||||
|
||||
struct xm_instrument_s {
|
||||
#if XM_STRINGS
|
||||
char name[INSTRUMENT_NAME_LENGTH + 1];
|
||||
#endif
|
||||
uint16_t num_samples;
|
||||
uint8_t sample_of_notes[NUM_NOTES];
|
||||
xm_envelope_t volume_envelope;
|
||||
xm_envelope_t panning_envelope;
|
||||
xm_waveform_type_t vibrato_type;
|
||||
uint8_t vibrato_sweep;
|
||||
uint8_t vibrato_depth;
|
||||
uint8_t vibrato_rate;
|
||||
uint16_t volume_fadeout;
|
||||
uint64_t latest_trigger;
|
||||
bool muted;
|
||||
|
||||
xm_sample_t* samples;
|
||||
};
|
||||
typedef struct xm_instrument_s xm_instrument_t;
|
||||
|
||||
struct xm_pattern_slot_s {
|
||||
uint16_t period; /* MOD raw Amiga period; 0 means XM note-based slot */
|
||||
uint8_t note; /* 1-96, 97 = Key Off note */
|
||||
uint8_t instrument; /* 1-128 */
|
||||
uint8_t volume_column;
|
||||
uint8_t effect_type;
|
||||
uint8_t effect_param;
|
||||
};
|
||||
typedef struct xm_pattern_slot_s xm_pattern_slot_t;
|
||||
|
||||
struct xm_pattern_s {
|
||||
uint16_t num_rows;
|
||||
xm_pattern_slot_t* slots; /* Array of size num_rows * num_channels */
|
||||
};
|
||||
typedef struct xm_pattern_s xm_pattern_t;
|
||||
|
||||
struct xm_mod_efx_backup_s {
|
||||
xm_sample_t* sample;
|
||||
int8_t* backup8;
|
||||
uint32_t loop_start;
|
||||
uint32_t loop_length;
|
||||
};
|
||||
typedef struct xm_mod_efx_backup_s xm_mod_efx_backup_t;
|
||||
|
||||
struct xm_module_s {
|
||||
#if XM_STRINGS
|
||||
char name[MODULE_NAME_LENGTH + 1];
|
||||
char trackername[TRACKER_NAME_LENGTH + 1];
|
||||
#endif
|
||||
uint16_t length;
|
||||
uint16_t restart_position;
|
||||
uint16_t num_channels;
|
||||
uint16_t num_patterns;
|
||||
uint16_t num_instruments;
|
||||
xm_frequency_type_t frequency_type;
|
||||
uint8_t pattern_table[PATTERN_ORDER_TABLE_LENGTH];
|
||||
|
||||
xm_pattern_t* patterns;
|
||||
xm_instrument_t* instruments; /* Instrument 1 has index 0,
|
||||
* instrument 2 has index 1, etc. */
|
||||
};
|
||||
typedef struct xm_module_s xm_module_t;
|
||||
|
||||
struct xm_channel_context_s {
|
||||
float note;
|
||||
float orig_note; /* The original note before effect modifications, as read in the pattern. */
|
||||
xm_instrument_t* instrument; /* Could be NULL */
|
||||
xm_sample_t* sample; /* Could be NULL */
|
||||
xm_pattern_slot_t* current;
|
||||
|
||||
float sample_position;
|
||||
float period;
|
||||
float frequency;
|
||||
float step;
|
||||
bool ping; /* For ping-pong samples: true is -->, false is <-- */
|
||||
|
||||
float volume; /* Ideally between 0 (muted) and 1 (loudest) */
|
||||
float channel_volume; /* S3M Mxx/Nxy channel global volume multiplier, 0..1. */
|
||||
float panning; /* Between 0 (left) and 1 (right); 0.5 is centered */
|
||||
float default_panning; /* < 0: use sample panning; >= 0: use channel default panning */
|
||||
bool surround; /* S3M XA4: inverse right channel phase around centered panning. */
|
||||
tracker_format_t tracker_format;
|
||||
float period_note_offset; /* MOD finetune offset for raw period slots */
|
||||
|
||||
uint16_t autovibrato_ticks;
|
||||
|
||||
bool sustained;
|
||||
float fadeout_volume;
|
||||
float volume_envelope_volume;
|
||||
float panning_envelope_panning;
|
||||
uint16_t volume_envelope_frame_count;
|
||||
uint16_t panning_envelope_frame_count;
|
||||
|
||||
float autovibrato_note_offset;
|
||||
|
||||
bool arp_in_progress;
|
||||
uint8_t arp_note_offset;
|
||||
uint8_t arpeggio_param;
|
||||
uint8_t volume_slide_param;
|
||||
uint8_t fine_volume_slide_param;
|
||||
uint8_t global_volume_slide_param;
|
||||
uint8_t channel_volume_slide_param;
|
||||
uint8_t panning_slide_param;
|
||||
uint8_t portamento_up_param;
|
||||
uint8_t portamento_down_param;
|
||||
uint8_t fine_portamento_up_param;
|
||||
uint8_t fine_portamento_down_param;
|
||||
uint8_t extra_fine_portamento_up_param;
|
||||
uint8_t extra_fine_portamento_down_param;
|
||||
uint8_t tone_portamento_param;
|
||||
float tone_portamento_target_period;
|
||||
uint8_t multi_retrig_param;
|
||||
uint8_t note_delay_param;
|
||||
uint8_t sample_offset_param;
|
||||
uint8_t s3m_high_sample_offset_param;
|
||||
uint8_t invert_loop_speed;
|
||||
uint16_t invert_loop_delay;
|
||||
uint32_t invert_loop_offset;
|
||||
uint8_t pattern_loop_origin; /* Where to restart a E6y loop */
|
||||
uint8_t pattern_loop_count; /* How many loop passes have been done */
|
||||
bool vibrato_in_progress;
|
||||
xm_waveform_type_t vibrato_waveform;
|
||||
bool vibrato_waveform_retrigger; /* True if a new note retriggers the waveform */
|
||||
uint8_t vibrato_param;
|
||||
uint16_t vibrato_ticks; /* Position in the waveform */
|
||||
float vibrato_note_offset;
|
||||
float vibrato_period_offset;
|
||||
xm_waveform_type_t tremolo_waveform;
|
||||
bool tremolo_waveform_retrigger;
|
||||
uint8_t tremolo_param;
|
||||
uint8_t tremolo_ticks;
|
||||
float tremolo_volume;
|
||||
xm_waveform_type_t panbrello_waveform;
|
||||
bool panbrello_waveform_retrigger;
|
||||
bool panbrello_in_progress;
|
||||
uint8_t panbrello_param;
|
||||
uint16_t panbrello_ticks;
|
||||
float panbrello_panning_offset;
|
||||
uint8_t tremor_param;
|
||||
uint8_t s3m_effect_memory;
|
||||
bool s3m_glissando_control;
|
||||
bool s3m_note_cut;
|
||||
bool tremor_on;
|
||||
uint8_t s3m_opl_key_on;
|
||||
uint8_t s3m_opl_instrument;
|
||||
uint32_t s3m_opl_trigger_id;
|
||||
uint32_t s3m_opl_synced_trigger_id;
|
||||
|
||||
uint64_t latest_trigger;
|
||||
bool muted;
|
||||
|
||||
#if XM_RAMPING
|
||||
/* These values are updated at the end of each tick, to save
|
||||
* a couple of float operations on every generated sample. */
|
||||
float target_panning;
|
||||
float target_volume;
|
||||
|
||||
unsigned long frame_count;
|
||||
float end_of_previous_sample[XM_SAMPLE_RAMPING_POINTS];
|
||||
#endif
|
||||
|
||||
float actual_panning;
|
||||
float actual_volume;
|
||||
};
|
||||
typedef struct xm_channel_context_s xm_channel_context_t;
|
||||
|
||||
struct xm_context_s {
|
||||
size_t ctx_size; /* Must be first, see xm_create_context_from_libxmize() */
|
||||
xm_module_t module;
|
||||
tracker_format_t tracker_format;
|
||||
bool s3m_fast_volume_slides;
|
||||
uint8_t s3m_tempo_slide_param;
|
||||
uint16_t s3m_adlib_instrument_count;
|
||||
s3m_adlib_instrument_t *s3m_adlib_instruments;
|
||||
uint8_t *s3m_channel_opl;
|
||||
uint32_t rate;
|
||||
|
||||
uint16_t tempo;
|
||||
uint16_t bpm;
|
||||
float global_volume;
|
||||
float amplification;
|
||||
|
||||
#if XM_RAMPING
|
||||
/* How much is a channel final volume allowed to change per
|
||||
* sample; this is used to avoid abrubt volume changes which
|
||||
* manifest as "clicks" in the generated sound. */
|
||||
float volume_ramp;
|
||||
float panning_ramp; /* Same for panning. */
|
||||
#endif
|
||||
|
||||
uint8_t current_table_index;
|
||||
uint8_t current_row;
|
||||
uint16_t current_tick; /* Can go below 255, with high tempo and a pattern delay */
|
||||
float remaining_samples_in_tick;
|
||||
uint64_t generated_samples;
|
||||
|
||||
bool position_jump;
|
||||
bool pattern_break;
|
||||
uint8_t jump_dest;
|
||||
uint8_t jump_row;
|
||||
|
||||
/* Extra ticks to be played before going to the next row -
|
||||
* Used for EEy effect */
|
||||
uint16_t extra_ticks;
|
||||
|
||||
uint8_t* row_loop_count; /* Array of size MAX_NUM_ROWS * module_length */
|
||||
uint8_t loop_count;
|
||||
uint8_t max_loop_count;
|
||||
|
||||
xm_channel_context_t* channels;
|
||||
|
||||
uint16_t mod_efx_backup_count;
|
||||
xm_mod_efx_backup_t* mod_efx_backups;
|
||||
int8_t* mod_efx_backup_data;
|
||||
|
||||
uint32_t tracker_segments_magic;
|
||||
uint32_t tracker_segments_magic_check;
|
||||
uint16_t tracker_segment_count;
|
||||
tracker_context_segment_page_t* tracker_segment_pages;
|
||||
};
|
||||
|
||||
/* ----- Internal API ----- */
|
||||
|
||||
/** Check the module data for errors/inconsistencies.
|
||||
*
|
||||
* @returns 0 if everything looks OK. Module should be safe to load.
|
||||
*/
|
||||
int xm_check_sanity_preload(const char*, size_t);
|
||||
|
||||
/** Check a loaded module for errors/inconsistencies.
|
||||
*
|
||||
* @returns 0 if everything looks OK.
|
||||
*/
|
||||
int xm_check_sanity_postload(xm_context_t*);
|
||||
|
||||
/** Get the number of bytes needed to store the module data in a
|
||||
* dynamically allocated blank context.
|
||||
*
|
||||
* Things that are dynamically allocated:
|
||||
* - sample data
|
||||
* - sample structures in instruments
|
||||
* - pattern data
|
||||
* - row loop count arrays
|
||||
* - pattern structures in module
|
||||
* - instrument structures in module
|
||||
* - channel contexts
|
||||
* - context structure itself
|
||||
|
||||
* @returns 0 if everything looks OK.
|
||||
*/
|
||||
size_t xm_get_memory_needed_for_context(const char*, size_t);
|
||||
|
||||
/** Populate the context from module data.
|
||||
*
|
||||
* @returns non-zero on success
|
||||
*/
|
||||
int xm_load_module(xm_context_t*, const char*, size_t, xm_context_group_cursor_t*);
|
||||
|
||||
// ============================================================================
|
||||
// MOD module support
|
||||
// ============================================================================
|
||||
|
||||
#define MOD_TITLE_SIZE 20
|
||||
#define MOD_SAMPLE_COUNT 31
|
||||
#define MOD_15_SAMPLE_COUNT 15
|
||||
#define MOD_15_SONG_LENGTH_OFFSET 470
|
||||
#define MOD_15_RESTART_OFFSET 471
|
||||
#define MOD_15_ORDER_OFFSET 472
|
||||
#define MOD_15_HEADER_SIZE 600
|
||||
#define MOD_SAMPLE_HEADER_SIZE 30
|
||||
#define MOD_SONG_LENGTH_OFFSET 950
|
||||
#define MOD_RESTART_OFFSET 951
|
||||
#define MOD_ORDER_OFFSET 952
|
||||
#define MOD_ORDER_COUNT 128
|
||||
#define MOD_MAGIC_OFFSET 1080
|
||||
#define MOD_HEADER_SIZE 1084
|
||||
#define MOD_ROWS_PER_PATTERN 64
|
||||
#define MOD_DEFAULT_TEMPO 6
|
||||
#define MOD_DEFAULT_BPM 125
|
||||
#define MOD_MAX_CHANNELS 32
|
||||
|
||||
struct xm_module_s;
|
||||
struct xm_pattern_slot_s;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint16_t channels;
|
||||
uint16_t patterns;
|
||||
uint16_t song_length;
|
||||
uint16_t restart_position;
|
||||
uint16_t sample_count;
|
||||
size_t header_size;
|
||||
size_t pattern_data_offset;
|
||||
size_t sample_data_offset;
|
||||
uint32_t sample_length[MOD_SAMPLE_COUNT];
|
||||
uint32_t sample_loop_start[MOD_SAMPLE_COUNT];
|
||||
uint32_t sample_loop_length[MOD_SAMPLE_COUNT];
|
||||
uint32_t sample_file_length[MOD_SAMPLE_COUNT];
|
||||
size_t pattern_bytes;
|
||||
size_t sample_bytes;
|
||||
size_t expected_size;
|
||||
uint32_t efx_sample_mask;
|
||||
uint16_t efx_backup_count;
|
||||
size_t efx_backup_bytes;
|
||||
} mod_layout_t;
|
||||
|
||||
int mod_create_context_safe(xm_context_t **ctxp, void *moddata, size_t moddata_length, uint32_t rate, MFUNC mfunc, size_t *out_bytes_needed);
|
||||
int mod_check_sanity_preload(const void *moddata, size_t moddata_length);
|
||||
uint16_t mod_get_channel_count(const void *moddata, size_t moddata_length);
|
||||
int mod_read_layout_from_header(const uint8_t *header, size_t header_length, size_t moddata_length, mod_layout_t *layout);
|
||||
size_t mod_get_memory_needed_for_layout(const mod_layout_t *layout);
|
||||
int mod_layout_set_efx_backup_mask(mod_layout_t *layout, uint32_t sample_mask);
|
||||
int mod_layout_set_efx_backup_all_looped(mod_layout_t *layout);
|
||||
uint32_t mod_scan_efx_sample_mask_from_context(const xm_context_t *ctx, const mod_layout_t *layout);
|
||||
uint32_t mod_scan_efx_sample_mask_from_data(const void *moddata, const mod_layout_t *layout);
|
||||
int mod_get_group_sizes_for_layout(const mod_layout_t *layout, xm_context_group_sizes_t *sizes);
|
||||
int mod_setup_module_header(xm_context_t *ctx, const uint8_t *header, const mod_layout_t *layout, xm_context_group_cursor_t *cursor);
|
||||
int mod_setup_sample_metadata(xm_context_t *ctx, const uint8_t *header, const mod_layout_t *layout, uint16_t i, xm_context_group_cursor_t *cursor, MFUNC mfunc);
|
||||
void mod_decode_pattern_slot(struct xm_pattern_slot_s *slot, const uint8_t *entry);
|
||||
int mod_setup_context_runtime(xm_context_t *ctx, xm_context_group_cursor_t *cursor, uint32_t rate);
|
||||
int mod_setup_efx_backups(xm_context_t *ctx, const mod_layout_t *layout, xm_context_group_cursor_t *cursor);
|
||||
void mod_restore_efx_backups(xm_context_t *ctx);
|
||||
int mod_finish_context(xm_context_t **ctxp, xm_context_t *ctx);
|
||||
|
||||
// ============================================================================
|
||||
// S3M module support
|
||||
// ============================================================================
|
||||
|
||||
#define S3M_HEADER_SIZE 96
|
||||
#define S3M_MAGIC_OFFSET 44
|
||||
#define S3M_MIN_CHANNELS 1
|
||||
#define S3M_MAX_CHANNELS 32
|
||||
#define S3M_MAX_ORDERS 256
|
||||
#define S3M_MAX_SAMPLES 99
|
||||
#define S3M_MAX_PATTERNS 256
|
||||
#define S3M_SAMPLE_HEADER_SIZE 80
|
||||
#define S3M_ROWS_PER_PATTERN 64
|
||||
#define S3M_DEFAULT_C4SPEED 8363
|
||||
|
||||
int s3m_create_context_safe(xm_context_t **ctxp, void *s3mdata, size_t s3mdata_length, uint32_t rate, MFUNC mfunc, size_t *out_bytes_needed);
|
||||
const char *s3m_get_last_error();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
// ============================================================================
|
||||
// ESP32 tracker task / CLI integration
|
||||
// ============================================================================
|
||||
|
||||
enum
|
||||
{
|
||||
TRACK_TASK_INIT,
|
||||
TRACK_TASK_PLAY,
|
||||
TRACK_TASK_STOP,
|
||||
TRACK_TASK_RESET,
|
||||
TRACK_TASK_SET_POS,
|
||||
TRACK_TASK_SET_PARAMS,
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t task;
|
||||
int handle;
|
||||
} TRACK_TASK;
|
||||
|
||||
// Records the last time a TRACK_PLAY/RESET/SET_PARAMS command was rejected with
|
||||
// ESP_ERR_INV_XM_HANDLE, since that check runs in ISR context (spi_slave.cpp)
|
||||
// where ESP_LOGx isn't safe to call. Inspect via the "xm lasterr" console command.
|
||||
typedef struct
|
||||
{
|
||||
volatile bool valid;
|
||||
volatile uint8_t cmd;
|
||||
volatile int handle;
|
||||
volatile bool handle_in_range;
|
||||
volatile uint8_t obj_type;
|
||||
volatile int64_t timestamp_us; // esp_timer_get_time() at rejection - directly comparable to xm_task's own TIMING logs
|
||||
} TrackHandleErrorInfo;
|
||||
|
||||
extern TrackHandleErrorInfo g_track_handle_error;
|
||||
|
||||
void record_track_handle_error(uint8_t cmd, int handle);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t task;
|
||||
xm_context_t *ctx;
|
||||
} PLAYER_TASK;
|
||||
|
||||
extern QueueHandle_t xm_queue;
|
||||
extern int master_volume;
|
||||
extern int mix_volume;
|
||||
extern int curr_xm_handle;
|
||||
|
||||
int xm_cmd(int argc, char **argv);
|
||||
void xm_task(void *arg);
|
||||
void initialize_xm();
|
||||
void xm_console_register_system_commands();
|
||||
|
||||
// Pause/resume XM tracker playback AND the shared i2s_task/player_task
|
||||
// pipeline it renders through (also used by SFX and OPL, see their
|
||||
// player_task usage) - used while the SMB2/3 server is active.
|
||||
void tracker_suspend();
|
||||
void tracker_resume();
|
||||
int xm_load_play_file(const char *path, bool quiet = false);
|
||||
int xm_play_cmd(int handle, bool quiet = false);
|
||||
int xm_stop_cmd(bool quiet = false);
|
||||
int xm_reset_cmd(int handle, bool quiet = false);
|
||||
esp_err_t xm_host_stream_prepare_command(size_t module_size, size_t rx_size);
|
||||
esp_err_t mod_host_stream_prepare_command(size_t module_size, size_t rx_size);
|
||||
esp_err_t s3m_host_stream_prepare_command(size_t module_size, size_t rx_size);
|
||||
void xm_host_stream_abort_current();
|
||||
void xm_host_stream_process_rx_data(const uint8_t *data, size_t size);
|
||||
|
||||
#endif
|
||||
+1195
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
#include "tsspiffs.h"
|
||||
|
||||
#define TSF_PART_LABEL "tsf"
|
||||
#define TSF_BASE_PATH "/tsf"
|
||||
#define TSF_PART_SUBTYPE_VALUE 0x84
|
||||
#define TSF_PART_SUBTYPE_NAME "tsf"
|
||||
#define TSF_BLOCK_SIZE 4096
|
||||
#define TSF_HAL_BUF_SIZE 256
|
||||
#define TSF_VFS_MAX_FILES 4
|
||||
|
||||
extern bool tsf_storage_initialized;
|
||||
extern bool tsf_storage_mounted;
|
||||
extern char tsf_storage_base_path[32];
|
||||
extern char tsf_storage_part_label[16];
|
||||
|
||||
esp_err_t tsf_storage_mount();
|
||||
esp_err_t tsf_storage_mount_quiet();
|
||||
esp_err_t tsf_storage_format();
|
||||
esp_err_t tsf_storage_delete();
|
||||
esp_err_t tsf_storage_init();
|
||||
esp_err_t tsf_storage_ensure_ready();
|
||||
esp_err_t tsf_storage_ensure_ready_quiet();
|
||||
void tsf_storage_deinit();
|
||||
|
||||
TSF_VOLUME *tsf_storage_volume();
|
||||
esp_err_t tsf_storage_space_info(uint64_t *total_bytes, uint64_t *free_bytes);
|
||||
bool tsf_storage_path_has_base(const char *path);
|
||||
esp_err_t tsf_storage_name_from_path(const char *path, char *out, size_t out_size);
|
||||
bool tsf_storage_path_is_root(const char *path);
|
||||
esp_err_t tsf_storage_open_read(const char *path, TSF_FILE *file, size_t *out_size);
|
||||
esp_err_t tsf_storage_read_file(const char *path, void *dst, size_t size, size_t *out_size);
|
||||
esp_err_t tsf_storage_list_dir(const char *path);
|
||||
esp_err_t tsf_storage_result_to_err(TSF_RESULT res);
|
||||
const char *tsf_storage_result_name(TSF_RESULT res);
|
||||
const char *tsf_storage_subtype_name();
|
||||
esp_err_t tsf_storage_register_vfs();
|
||||
|
||||
int tsf_storage_info_cmd(int argc, char **argv);
|
||||
int tsf_storage_format_cmd(int argc, char **argv);
|
||||
int tsf_storage_delete_cmd(int argc, char **argv);
|
||||
int tsf_storage_ls_cmd(int argc, char **argv);
|
||||
int tsf_storage_cmd(int argc, char **argv);
|
||||
|
||||
void tsf_console_register_system_commands();
|
||||
@@ -0,0 +1,517 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
#include "tsspiffs.h"
|
||||
|
||||
TSF_RESULT tsf_format(TSF_CONFIG *cfg)
|
||||
{
|
||||
if (!cfg || !cfg->block_size || !cfg->bulk_size) return TSF_RES_FS_ERROR;
|
||||
if ((cfg->bulk_size % cfg->block_size) != 0) return TSF_RES_FS_ERROR;
|
||||
if (cfg->block_size <= sizeof(TSF_CHUNK)) return TSF_RES_FS_ERROR;
|
||||
if (!cfg->hal_read_func || !cfg->hal_write_func) return TSF_RES_FS_ERROR;
|
||||
if (!cfg->hal_erase_func && !cfg->hal_erase_range_func) return TSF_RES_FS_ERROR;
|
||||
|
||||
return tsf_init_chunk_range(cfg, cfg->bulk_start, cfg->bulk_size);
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_init_chunk(TSF_CONFIG *cfg, u32 addr)
|
||||
{
|
||||
if (!cfg) return TSF_RES_FS_ERROR;
|
||||
return tsf_init_chunk_range(cfg, addr, cfg->block_size);
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_init_erased_chunk(TSF_CONFIG *cfg, u32 addr)
|
||||
{
|
||||
if (!cfg || !cfg->block_size) return TSF_RES_FS_ERROR;
|
||||
if (!cfg->hal_write_func) return TSF_RES_FS_ERROR;
|
||||
|
||||
#ifdef TSF_CHECK_BLANK
|
||||
TSF_RESULT rc = tsf_check_blank(cfg, addr, cfg->block_size);
|
||||
if (rc != TSF_RES_OK)
|
||||
return rc;
|
||||
#endif
|
||||
|
||||
u32 magic = TSF_MAGIC;
|
||||
cfg->hal_write_func(addr, &magic, sizeof(magic));
|
||||
|
||||
return TSF_RES_OK;
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_init_chunk_range(TSF_CONFIG *cfg, u32 addr, u32 size)
|
||||
{
|
||||
TSF_RESULT rc;
|
||||
|
||||
if (!cfg || !cfg->block_size || !size) return TSF_RES_FS_ERROR;
|
||||
if ((size % cfg->block_size) != 0) return TSF_RES_FS_ERROR;
|
||||
if (!cfg->hal_erase_func && !cfg->hal_erase_range_func) return TSF_RES_FS_ERROR;
|
||||
|
||||
if (cfg->hal_erase_range_func)
|
||||
{
|
||||
rc = cfg->hal_erase_range_func(addr, size);
|
||||
if (rc != TSF_RES_OK)
|
||||
return rc;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 pos = 0; pos < size; pos += cfg->block_size)
|
||||
{
|
||||
rc = cfg->hal_erase_func(addr + pos);
|
||||
if (rc != TSF_RES_OK)
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 pos = 0; pos < size; pos += cfg->block_size)
|
||||
{
|
||||
rc = tsf_init_erased_chunk(cfg, addr + pos);
|
||||
if (rc != TSF_RES_OK)
|
||||
return rc;
|
||||
}
|
||||
|
||||
return TSF_RES_OK;
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_check_blank(TSF_CONFIG *cfg, u32 addr, u32 size)
|
||||
{
|
||||
if (!cfg || !cfg->buf || !cfg->buf_size) return TSF_RES_FS_ERROR;
|
||||
|
||||
while (size)
|
||||
{
|
||||
u16 sz = (u16)min(cfg->buf_size, size);
|
||||
u8 *buf = cfg->buf;
|
||||
cfg->hal_read_func(addr, buf, sz);
|
||||
|
||||
for (u16 j = 0; j < sz; j++)
|
||||
if (*buf++ != 0xFF)
|
||||
return TSF_RES_NOT_BLANK;
|
||||
|
||||
size -= sz;
|
||||
addr += sz;
|
||||
}
|
||||
|
||||
return TSF_RES_OK;
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_take_new_chunk(TSF_VOLUME *vol, u8 type, u32 *chunk_addr)
|
||||
{
|
||||
if (!vol || !vol->cfg || !chunk_addr) return TSF_RES_FS_ERROR;
|
||||
if (!vol->free) return TSF_RES_BULK_FULL; // to speed up the search
|
||||
|
||||
TSF_CONFIG *cfg = vol->cfg;
|
||||
|
||||
u16 chunk_num = cfg->last_written_chunk;
|
||||
u32 addr = chunk_addr(chunk_num);
|
||||
TSF_CHUNK chunk;
|
||||
|
||||
do
|
||||
{
|
||||
cfg->hal_read_func(addr, &chunk, sizeof(TSF_CHUNK));
|
||||
|
||||
if ((chunk.magic == TSF_MAGIC) && (chunk.type == TSF_CHUNK_FREE))
|
||||
{
|
||||
*chunk_addr = addr;
|
||||
cfg->last_written_chunk = chunk_num;
|
||||
vol->free -= cfg->block_size;
|
||||
|
||||
cfg->hal_write_func(addr + offsetof(TSF_CHUNK, type), &type, sizeof(type));
|
||||
|
||||
return TSF_RES_OK;
|
||||
}
|
||||
|
||||
addr += cfg->block_size;
|
||||
chunk_num++;
|
||||
|
||||
if (addr >= cfg->bulk_start + cfg->bulk_size)
|
||||
{
|
||||
addr = cfg->bulk_start;
|
||||
chunk_num = 0;
|
||||
}
|
||||
} while (chunk_num != cfg->last_written_chunk);
|
||||
|
||||
return TSF_RES_BULK_FULL;
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_mount(TSF_CONFIG *cfg, TSF_VOLUME *vol)
|
||||
{
|
||||
if (!cfg || !vol) return TSF_RES_FS_ERROR;
|
||||
if (!cfg->block_size || !cfg->bulk_size) return TSF_RES_FS_ERROR;
|
||||
if ((cfg->bulk_size % cfg->block_size) != 0) return TSF_RES_FS_ERROR;
|
||||
if (cfg->block_size <= sizeof(TSF_CHUNK)) return TSF_RES_FS_ERROR;
|
||||
if (!cfg->hal_read_func || !cfg->hal_write_func) return TSF_RES_FS_ERROR;
|
||||
if (!cfg->hal_erase_func && !cfg->hal_erase_range_func) return TSF_RES_FS_ERROR;
|
||||
|
||||
cfg->last_written_chunk = 0;
|
||||
vol->cfg = cfg;
|
||||
vol->list_addr = cfg->bulk_start;
|
||||
|
||||
return tsf_vol_stat(vol);
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_vol_stat(TSF_VOLUME *vol)
|
||||
{
|
||||
if (!vol || !vol->cfg) return TSF_RES_FS_ERROR;
|
||||
|
||||
TSF_CONFIG *cfg = vol->cfg;
|
||||
vol->free = 0;
|
||||
vol->chunks_number = 0;
|
||||
vol->files_number = 0;
|
||||
|
||||
for (u32 i = 0; i < cfg->bulk_size; i += cfg->block_size)
|
||||
{
|
||||
TSF_CHUNK chunk;
|
||||
cfg->hal_read_func(i + cfg->bulk_start, &chunk, sizeof(TSF_CHUNK));
|
||||
|
||||
if (chunk.magic == TSF_MAGIC)
|
||||
{
|
||||
vol->chunks_number++;
|
||||
|
||||
if (chunk.type == TSF_CHUNK_FREE)
|
||||
vol->free += cfg->block_size;
|
||||
|
||||
else if (chunk.type == (u8)TSF_CHUNK_HEAD)
|
||||
vol->files_number++;
|
||||
}
|
||||
}
|
||||
|
||||
return TSF_RES_OK;
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_search(TSF_VOLUME *vol, u32 *chunk_addr, const char *name)
|
||||
{
|
||||
if (!vol || !vol->cfg || !chunk_addr || !name) return TSF_RES_FS_ERROR;
|
||||
|
||||
TSF_CONFIG *cfg = vol->cfg;
|
||||
size_t name_len = strlen(name);
|
||||
if (name_len > 255) return TSF_RES_FS_ERROR;
|
||||
if (name_len && (!cfg->buf || (cfg->buf_size < name_len))) return TSF_RES_FS_ERROR;
|
||||
|
||||
char search_name[256];
|
||||
memcpy(search_name, name, name_len);
|
||||
search_name[name_len] = 0;
|
||||
|
||||
u8 fnlen = (u8)name_len;
|
||||
|
||||
for (u32 i = 0; i < cfg->bulk_size; i += cfg->block_size)
|
||||
{
|
||||
u32 addr = i + cfg->bulk_start;
|
||||
TSF_CHUNK chunk;
|
||||
cfg->hal_read_func(addr, &chunk, sizeof(TSF_CHUNK));
|
||||
|
||||
if ((chunk.magic != TSF_MAGIC) || (chunk.type != (u8)TSF_CHUNK_HEAD))
|
||||
continue;
|
||||
|
||||
u8 stored_fnlen;
|
||||
cfg->hal_read_func(addr + sizeof(TSF_CHUNK) + offsetof(TSF_HDR, fnlen), &stored_fnlen, sizeof(stored_fnlen));
|
||||
|
||||
if (fnlen != stored_fnlen)
|
||||
continue;
|
||||
|
||||
if (fnlen)
|
||||
{
|
||||
cfg->hal_read_func(addr + sizeof(TSF_CHUNK) + sizeof(TSF_HDR), cfg->buf, fnlen);
|
||||
|
||||
if (memcmp(search_name, cfg->buf, fnlen))
|
||||
continue;
|
||||
}
|
||||
|
||||
*chunk_addr = addr;
|
||||
|
||||
return TSF_RES_OK;
|
||||
}
|
||||
|
||||
return TSF_RES_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_open(TSF_VOLUME *vol, TSF_FILE *file, const char *name, u8 mode)
|
||||
{
|
||||
if (!vol || !file || !name) return TSF_RES_FS_ERROR;
|
||||
|
||||
file->mode = mode;
|
||||
file->vol = vol;
|
||||
|
||||
if (mode == TSF_MODE_READ)
|
||||
return tsf_open_for_read(file, name);
|
||||
else if (mode == TSF_MODE_CREATE_WRITE)
|
||||
return tsf_create(file, name);
|
||||
else
|
||||
return TSF_RES_MODE_ERROR;
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_open_for_read(TSF_FILE *file, const char *name)
|
||||
{
|
||||
TSF_VOLUME *vol = file->vol;
|
||||
u32 addr;
|
||||
|
||||
TSF_RESULT rc = tsf_search(vol, &addr, name);
|
||||
if (rc != TSF_RES_OK)
|
||||
return rc;
|
||||
|
||||
file->addr = file->chunk_addr = addr;
|
||||
vol->cfg->hal_read_func(addr + sizeof(TSF_CHUNK) + offsetof(TSF_HDR, size), &file->size, sizeof(file->size));
|
||||
file->seek = 0;
|
||||
file->chunk_offset = sizeof(TSF_CHUNK) + sizeof(TSF_HDR) + (u8)strlen(name);
|
||||
vol->cfg->hal_read_func(addr + offsetof(TSF_CHUNK, next_chunk), &file->next_chunk, sizeof(file->next_chunk));
|
||||
|
||||
return TSF_RES_OK;
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_create(TSF_FILE *file, const char *name)
|
||||
{
|
||||
TSF_VOLUME *vol = file->vol;
|
||||
u32 addr;
|
||||
size_t name_len = strlen(name);
|
||||
if (name_len > 255) return TSF_RES_FS_ERROR;
|
||||
|
||||
TSF_CONFIG *cfg = vol->cfg;
|
||||
if ((sizeof(TSF_CHUNK) + sizeof(TSF_HDR) + name_len) > cfg->block_size)
|
||||
return TSF_RES_FS_ERROR;
|
||||
|
||||
u8 fnlen = (u8)name_len;
|
||||
|
||||
#ifdef TSF_CHECK_EXIST_ON_CREATE
|
||||
{
|
||||
TSF_RESULT rc = tsf_search(vol, &addr, name);
|
||||
|
||||
if (rc != TSF_RES_FILE_NOT_FOUND)
|
||||
{
|
||||
if (rc == TSF_RES_OK)
|
||||
return TSF_RES_FILE_EXISTS;
|
||||
else
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (tsf_take_new_chunk(vol, TSF_CHUNK_HEAD, &addr) != TSF_RES_OK)
|
||||
return TSF_RES_BULK_FULL;
|
||||
|
||||
file->addr = addr;
|
||||
file->size = 0;
|
||||
file->seek = 0;
|
||||
file->chunk_addr = addr;
|
||||
file->chunk_offset = sizeof(TSF_CHUNK) + sizeof(TSF_HDR) + fnlen;
|
||||
file->prev_chunk_addr = addr;
|
||||
file->next_chunk = 0xFFFF;
|
||||
|
||||
addr += sizeof(TSF_CHUNK);
|
||||
TSF_HDR hdr;
|
||||
hdr.size = 0xFFFFFFFF;
|
||||
hdr.fnlen = fnlen;
|
||||
cfg->hal_write_func(addr, &hdr, sizeof(TSF_HDR));
|
||||
addr += sizeof(TSF_HDR);
|
||||
cfg->hal_write_func(addr, name, fnlen);
|
||||
|
||||
return TSF_RES_OK;
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_close(TSF_FILE *file)
|
||||
{
|
||||
if ((file->mode & TSF_MODE_WRITE) == TSF_MODE_WRITE)
|
||||
{
|
||||
file->vol->cfg->hal_write_func(file->addr + sizeof(TSF_CHUNK) + offsetof(TSF_HDR, size), &file->size, sizeof(file->size)); // save file size
|
||||
file->vol->files_number++;
|
||||
}
|
||||
|
||||
return TSF_RES_OK;
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_read(TSF_FILE *file, void *buf, u32 size)
|
||||
{
|
||||
return tsf_read_int(file, buf, size);
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_seek(TSF_FILE *file, u32 size)
|
||||
{
|
||||
return tsf_read_int(file, 0, size);
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_read_int(TSF_FILE *file, void *buf, u32 size)
|
||||
{
|
||||
if (!file || !file->vol || !file->vol->cfg) return TSF_RES_FS_ERROR;
|
||||
|
||||
u8 *data = (u8*)buf;
|
||||
TSF_CONFIG *cfg = file->vol->cfg;
|
||||
u32 addr = file->chunk_addr;
|
||||
if (file->seek >= file->size) return TSF_RES_OK;
|
||||
size = min(size, file->size - file->seek);
|
||||
|
||||
while (size)
|
||||
{
|
||||
if (file->chunk_offset == cfg->block_size) // new chunk
|
||||
{
|
||||
if (file->next_chunk == 0xFFFF) return TSF_RES_FS_ERROR;
|
||||
|
||||
addr = file->chunk_addr = chunk_addr(file->next_chunk);
|
||||
file->chunk_offset = sizeof(TSF_CHUNK);
|
||||
cfg->hal_read_func(addr + offsetof(TSF_CHUNK, next_chunk), &file->next_chunk, sizeof(file->next_chunk));
|
||||
}
|
||||
|
||||
u16 sz = (u16)min(cfg->block_size - file->chunk_offset, size);
|
||||
|
||||
if (buf)
|
||||
{
|
||||
cfg->hal_read_func(file->chunk_addr + file->chunk_offset, data, sz);
|
||||
data += sz;
|
||||
}
|
||||
|
||||
file->seek += sz;
|
||||
file->chunk_offset += sz;
|
||||
size -= sz;
|
||||
}
|
||||
|
||||
return TSF_RES_OK;
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_write(TSF_FILE *file, const void *buf, u32 size)
|
||||
{
|
||||
if (!file || !file->vol || !file->vol->cfg) return TSF_RES_FS_ERROR;
|
||||
if (size && !buf) return TSF_RES_FS_ERROR;
|
||||
|
||||
const u8 *data = (const u8*)buf;
|
||||
TSF_CONFIG *cfg = file->vol->cfg;
|
||||
u32 addr = file->chunk_addr;
|
||||
|
||||
while (size)
|
||||
{
|
||||
if (file->chunk_offset == cfg->block_size) // new chunk
|
||||
{
|
||||
if (tsf_take_new_chunk(file->vol, TSF_CHUNK_BODY, &addr) != TSF_RES_OK)
|
||||
return TSF_RES_BULK_FULL;
|
||||
|
||||
file->chunk_addr = addr;
|
||||
file->chunk_offset = sizeof(TSF_CHUNK);
|
||||
|
||||
cfg->hal_write_func(file->prev_chunk_addr + offsetof(TSF_CHUNK, next_chunk), &cfg->last_written_chunk, sizeof(cfg->last_written_chunk)); // save next chunk address to the previous one, cfg->last_written_chunk is set after tsf_take_new_chunk()
|
||||
}
|
||||
|
||||
u16 sz = (u16)min(cfg->block_size - file->chunk_offset, size);
|
||||
cfg->hal_write_func(file->chunk_addr + file->chunk_offset, data, sz);
|
||||
file->seek += sz;
|
||||
file->size += sz;
|
||||
file->chunk_offset += sz;
|
||||
data += sz;
|
||||
size -= sz;
|
||||
|
||||
if (file->chunk_offset == cfg->block_size) // close current chunk
|
||||
file->prev_chunk_addr = file->chunk_addr;
|
||||
}
|
||||
|
||||
return TSF_RES_OK;
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_delete_chunk_range(TSF_VOLUME *vol, u32 addr, u32 blocks)
|
||||
{
|
||||
TSF_RESULT rc;
|
||||
|
||||
if (!vol || !vol->cfg || !blocks) return TSF_RES_FS_ERROR;
|
||||
|
||||
rc = tsf_init_chunk_range(vol->cfg, addr, blocks * vol->cfg->block_size);
|
||||
if (rc != TSF_RES_OK)
|
||||
return rc;
|
||||
|
||||
vol->free += blocks * vol->cfg->block_size;
|
||||
return TSF_RES_OK;
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_delete(TSF_VOLUME *vol, const char *name)
|
||||
{
|
||||
if (!vol || !vol->cfg || !name) return TSF_RES_FS_ERROR;
|
||||
|
||||
u32 addr;
|
||||
u16 next;
|
||||
u32 range_addr = 0;
|
||||
u32 range_blocks = 0;
|
||||
TSF_CONFIG *cfg = vol->cfg;
|
||||
|
||||
TSF_RESULT rc = tsf_search(vol, &addr, name);
|
||||
if (rc != TSF_RES_OK)
|
||||
return rc;
|
||||
|
||||
do
|
||||
{
|
||||
u32 cur_addr = addr;
|
||||
|
||||
cfg->hal_read_func(addr + offsetof(TSF_CHUNK, next_chunk), &next, sizeof(next));
|
||||
|
||||
if (!range_blocks)
|
||||
{
|
||||
range_addr = cur_addr;
|
||||
range_blocks = 1;
|
||||
}
|
||||
else if (cur_addr == range_addr + range_blocks * cfg->block_size)
|
||||
{
|
||||
range_blocks++;
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = tsf_delete_chunk_range(vol, range_addr, range_blocks);
|
||||
if (rc != TSF_RES_OK)
|
||||
return rc;
|
||||
|
||||
range_addr = cur_addr;
|
||||
range_blocks = 1;
|
||||
}
|
||||
|
||||
if (next != 0xFFFF)
|
||||
addr = chunk_addr(next);
|
||||
} while (next != 0xFFFF);
|
||||
|
||||
rc = tsf_delete_chunk_range(vol, range_addr, range_blocks);
|
||||
if (rc != TSF_RES_OK)
|
||||
return rc;
|
||||
|
||||
if (vol->files_number)
|
||||
vol->files_number--;
|
||||
|
||||
return TSF_RES_OK;
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_stat(TSF_VOLUME *vol, TSF_FILE_STAT *stat, const char *name)
|
||||
{
|
||||
if (!vol || !stat || !name) return TSF_RES_FS_ERROR;
|
||||
|
||||
u32 addr;
|
||||
|
||||
TSF_RESULT rc = tsf_search(vol, &addr, name);
|
||||
if (rc != TSF_RES_OK)
|
||||
return rc;
|
||||
|
||||
vol->cfg->hal_read_func(addr + sizeof(TSF_CHUNK) + offsetof(TSF_HDR, size), &stat->size, sizeof(stat->size));
|
||||
return TSF_RES_OK;
|
||||
}
|
||||
|
||||
TSF_RESULT tsf_list(TSF_VOLUME *vol, u8 flag)
|
||||
{
|
||||
if (!vol || !vol->cfg) return TSF_RES_FS_ERROR;
|
||||
|
||||
TSF_CONFIG *cfg = vol->cfg;
|
||||
if (!cfg->buf || !cfg->buf_size) return TSF_RES_FS_ERROR;
|
||||
|
||||
if (flag == TSF_LIST_START)
|
||||
{
|
||||
vol->list_addr = cfg->bulk_start;
|
||||
return TSF_RES_OK;
|
||||
}
|
||||
|
||||
for (u32 addr = vol->list_addr; addr < (cfg->bulk_start + cfg->bulk_size); addr += cfg->block_size)
|
||||
{
|
||||
TSF_CHUNK chunk;
|
||||
cfg->hal_read_func(addr, &chunk, sizeof(TSF_CHUNK));
|
||||
|
||||
if ((chunk.magic != TSF_MAGIC) || (chunk.type != (u8)TSF_CHUNK_HEAD))
|
||||
continue;
|
||||
|
||||
u8 fnlen;
|
||||
cfg->hal_read_func(addr + sizeof(TSF_CHUNK) + offsetof(TSF_HDR, fnlen), &fnlen, sizeof(fnlen));
|
||||
fnlen = min(fnlen, cfg->buf_size - 1);
|
||||
cfg->hal_read_func(addr + sizeof(TSF_CHUNK) + sizeof(TSF_HDR), cfg->buf, fnlen);
|
||||
cfg->buf[fnlen] = 0;
|
||||
|
||||
vol->list_addr = addr + cfg->block_size;
|
||||
|
||||
return TSF_RES_OK;
|
||||
}
|
||||
|
||||
return TSF_RES_NO_MORE_FILES;
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// #define TSF_CHECK_BLANK // to test block after erase
|
||||
// #define TSF_CHECK_EXIST_ON_CREATE // to look for existing filename when file is created
|
||||
|
||||
typedef int64_t s64;
|
||||
typedef uint64_t u64;
|
||||
typedef int32_t s32;
|
||||
typedef uint32_t u32;
|
||||
typedef int16_t s16;
|
||||
typedef uint16_t u16;
|
||||
typedef int8_t s8;
|
||||
typedef uint8_t u8;
|
||||
|
||||
#ifndef min
|
||||
#define min(x,y) ((x) < (y) ? (x) : (y))
|
||||
#endif
|
||||
#ifndef max
|
||||
#define max(x,y) ((x) > (y) ? (x) : (y))
|
||||
#endif
|
||||
|
||||
// enums
|
||||
enum
|
||||
{
|
||||
TSF_MAGIC = ((u32)0x78E15CA3)
|
||||
};
|
||||
|
||||
typedef enum
|
||||
{
|
||||
TSF_RES_OK = 0,
|
||||
TSF_RES_FS_ERROR,
|
||||
TSF_RES_FILE_NOT_FOUND,
|
||||
TSF_RES_FILE_EXISTS,
|
||||
TSF_RES_MODE_ERROR,
|
||||
TSF_RES_BULK_FULL,
|
||||
TSF_RES_NOT_BLANK,
|
||||
TSF_RES_NO_MORE_FILES
|
||||
} TSF_RESULT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
TSF_CHUNK_HEAD = 0x00,
|
||||
TSF_CHUNK_BODY = 0x01,
|
||||
TSF_CHUNK_FREE = 0xFF
|
||||
} TSF_CHUNK_TYPE;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
TSF_MODE_READ = 0x01,
|
||||
TSF_MODE_WRITE = 0x02,
|
||||
TSF_MODE_CREATE = 0x04,
|
||||
TSF_MODE_CREATE_WRITE = TSF_MODE_CREATE | TSF_MODE_WRITE,
|
||||
} TSF_MODE;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
TSF_LIST_NEXT = 0,
|
||||
TSF_LIST_START
|
||||
} TSF_LIST;
|
||||
|
||||
// types
|
||||
typedef TSF_RESULT (*tsf_read_t)(u32, void*, u32);
|
||||
typedef TSF_RESULT (*tsf_write_t)(u32, const void*, u32);
|
||||
typedef TSF_RESULT (*tsf_erase_t)(u32);
|
||||
typedef TSF_RESULT (*tsf_erase_range_t)(u32, u32);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma pack(push, 1)
|
||||
#define TSF_PACKED
|
||||
#elif defined(__GNUC__)
|
||||
#define TSF_PACKED __attribute__((packed))
|
||||
#else
|
||||
#define TSF_PACKED
|
||||
#endif
|
||||
|
||||
typedef struct TSF_PACKED
|
||||
{
|
||||
u32 magic;
|
||||
u16 next_chunk;
|
||||
u8 type;
|
||||
} TSF_CHUNK;
|
||||
|
||||
typedef struct TSF_PACKED
|
||||
{
|
||||
u32 size;
|
||||
u8 fnlen;
|
||||
} TSF_HDR;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u32 bulk_start; // start address in flash IC
|
||||
u32 bulk_size; // size of the TSF
|
||||
u32 block_size; // erase block size
|
||||
u16 last_written_chunk; // for wear levelling
|
||||
|
||||
tsf_read_t hal_read_func;
|
||||
tsf_write_t hal_write_func;
|
||||
tsf_erase_t hal_erase_func;
|
||||
tsf_erase_range_t hal_erase_range_func;
|
||||
|
||||
u8 *buf;
|
||||
u16 buf_size;
|
||||
} TSF_CONFIG;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u32 free;
|
||||
u16 chunks_number;
|
||||
u16 files_number;
|
||||
u32 list_addr;
|
||||
TSF_CONFIG *cfg;
|
||||
} TSF_VOLUME;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u32 addr; // first chunk address
|
||||
u32 size; // file size
|
||||
u32 seek; // current pointer
|
||||
u32 chunk_addr; // current chunk address
|
||||
u32 chunk_offset; // current offset in chunk
|
||||
u32 prev_chunk_addr; // address of the previous chunk
|
||||
u16 next_chunk; // next chunk
|
||||
u8 mode;
|
||||
TSF_VOLUME *vol;
|
||||
} TSF_FILE;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u32 size;
|
||||
} TSF_FILE_STAT;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma pack(pop)
|
||||
#endif
|
||||
|
||||
#undef TSF_PACKED
|
||||
|
||||
// functions
|
||||
// file operations
|
||||
TSF_RESULT tsf_open(TSF_VOLUME*, TSF_FILE*, const char*, u8);
|
||||
TSF_RESULT tsf_read(TSF_FILE*, void*, u32);
|
||||
TSF_RESULT tsf_write(TSF_FILE*, const void*, u32);
|
||||
TSF_RESULT tsf_close(TSF_FILE*);
|
||||
TSF_RESULT tsf_seek(TSF_FILE*, u32);
|
||||
TSF_RESULT tsf_delete(TSF_VOLUME*, const char*);
|
||||
TSF_RESULT tsf_stat(TSF_VOLUME*, TSF_FILE_STAT*, const char*);
|
||||
TSF_RESULT tsf_list(TSF_VOLUME*, u8);
|
||||
|
||||
// volume operations
|
||||
TSF_RESULT tsf_mount(TSF_CONFIG*, TSF_VOLUME*);
|
||||
TSF_RESULT tsf_format(TSF_CONFIG*);
|
||||
TSF_RESULT tsf_check(TSF_CONFIG*);
|
||||
|
||||
// auxilliary functions
|
||||
TSF_RESULT tsf_init_chunk(TSF_CONFIG*, u32);
|
||||
TSF_RESULT tsf_init_erased_chunk(TSF_CONFIG*, u32);
|
||||
TSF_RESULT tsf_init_chunk_range(TSF_CONFIG*, u32, u32);
|
||||
TSF_RESULT tsf_check_blank(TSF_CONFIG*, u32, u32);
|
||||
TSF_RESULT tsf_search(TSF_VOLUME*, u32*, const char*);
|
||||
TSF_RESULT tsf_open_for_read(TSF_FILE*, const char*);
|
||||
TSF_RESULT tsf_create(TSF_FILE*, const char*);
|
||||
TSF_RESULT tsf_take_new_chunk(TSF_VOLUME*, u8, u32*);
|
||||
TSF_RESULT tsf_vol_stat(TSF_VOLUME*);
|
||||
TSF_RESULT tsf_read_int(TSF_FILE*, void*, u32);
|
||||
|
||||
#define chunk_addr(chunk) (cfg->bulk_start + ((chunk) * cfg->block_size))
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,434 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <math.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_console.h"
|
||||
#include "esp_chip_info.h"
|
||||
#include "esp_sleep.h"
|
||||
#include "esp_flash.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_private/esp_clk.h"
|
||||
#include <esp_crc.h>
|
||||
#include "driver/rtc_io.h"
|
||||
#include "driver/uart.h"
|
||||
#include "argtable3/argtable3.h"
|
||||
#include "sdkconfig.h"
|
||||
#include <esp_heap_caps.h>
|
||||
#include "esp_timer.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "usb/usb_host.h"
|
||||
#include "usb/hid_host.h"
|
||||
#include "usb/hid_usage_mouse.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "usb_mouse.h"
|
||||
#include "ps2_mouse.h"
|
||||
#include "nvs_params.h"
|
||||
|
||||
enum usb_mouse_evt_group_t
|
||||
{
|
||||
USB_MOUSE_EVT_QUIT = 0,
|
||||
USB_MOUSE_EVT_HID,
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
usb_mouse_evt_group_t event_group;
|
||||
struct
|
||||
{
|
||||
hid_host_device_handle_t handle;
|
||||
hid_host_driver_event_t event;
|
||||
void *arg;
|
||||
} hid;
|
||||
} usb_mouse_evt_t;
|
||||
|
||||
QueueHandle_t usb_mouse_evt_queue = NULL;
|
||||
TaskHandle_t usb_mouse_task_handle = NULL;
|
||||
TaskHandle_t usb_mouse_lib_task_handle = NULL;
|
||||
hid_host_device_handle_t usb_mouse_dev = NULL;
|
||||
volatile bool usb_mouse_mode_active = false;
|
||||
int usb_mouse_abs_x = 0;
|
||||
int usb_mouse_abs_y = 0;
|
||||
|
||||
void usb_mouse_report_callback(const uint8_t *data, int length)
|
||||
{
|
||||
if (length < (int)sizeof(hid_mouse_input_report_boot_t))
|
||||
return;
|
||||
|
||||
const hid_mouse_input_report_boot_t *r = (const hid_mouse_input_report_boot_t *)data;
|
||||
|
||||
usb_mouse_abs_x += r->x_displacement;
|
||||
usb_mouse_abs_y += r->y_displacement;
|
||||
|
||||
ps2_mouse_send_movement((int)r->x_displacement,
|
||||
-(int)r->y_displacement,
|
||||
(unsigned)r->buttons.val);
|
||||
}
|
||||
|
||||
void usb_mouse_interface_callback(hid_host_device_handle_t hid_device_handle, hid_host_interface_event_t event, void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
uint8_t data[64];
|
||||
size_t data_length = 0;
|
||||
hid_host_dev_params_t dev_params;
|
||||
|
||||
memset(data, 0, sizeof(data));
|
||||
|
||||
if (hid_host_device_get_params(hid_device_handle, &dev_params) != ESP_OK)
|
||||
return;
|
||||
|
||||
switch (event)
|
||||
{
|
||||
case HID_HOST_INTERFACE_EVENT_INPUT_REPORT:
|
||||
if (hid_host_device_get_raw_input_report_data(hid_device_handle, data, sizeof(data), &data_length) != ESP_OK)
|
||||
return;
|
||||
|
||||
if (dev_params.sub_class == HID_SUBCLASS_BOOT_INTERFACE && dev_params.proto == HID_PROTOCOL_MOUSE)
|
||||
usb_mouse_report_callback(data, (int)data_length);
|
||||
break;
|
||||
|
||||
case HID_HOST_INTERFACE_EVENT_DISCONNECTED:
|
||||
printf("usb mouse disconnected\r\n");
|
||||
if (usb_mouse_dev == hid_device_handle)
|
||||
usb_mouse_dev = NULL;
|
||||
usb_mouse_abs_x = 0;
|
||||
usb_mouse_abs_y = 0;
|
||||
hid_host_device_close(hid_device_handle);
|
||||
break;
|
||||
|
||||
case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR:
|
||||
printf("usb mouse transfer error\r\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void usb_mouse_process_device_event(hid_host_device_handle_t hid_device_handle, hid_host_driver_event_t event, void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
hid_host_dev_params_t dev_params;
|
||||
if (hid_host_device_get_params(hid_device_handle, &dev_params) != ESP_OK)
|
||||
return;
|
||||
|
||||
if (event != HID_HOST_DRIVER_EVENT_CONNECTED)
|
||||
return;
|
||||
|
||||
if (dev_params.proto != HID_PROTOCOL_MOUSE)
|
||||
{
|
||||
printf("usb hid connected, but not a mouse (proto=%d)\r\n", (int)dev_params.proto);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dev_params.sub_class != HID_SUBCLASS_BOOT_INTERFACE)
|
||||
{
|
||||
printf("usb mouse connected, but not boot protocol\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const hid_host_device_config_t dev_config =
|
||||
{
|
||||
.callback = usb_mouse_interface_callback,
|
||||
.callback_arg = NULL,
|
||||
};
|
||||
|
||||
if (hid_host_device_open(hid_device_handle, &dev_config) != ESP_OK)
|
||||
{
|
||||
printf("hid_host_device_open failed\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (hid_class_request_set_protocol(hid_device_handle, HID_REPORT_PROTOCOL_BOOT) != ESP_OK)
|
||||
{
|
||||
printf("hid_class_request_set_protocol failed\r\n");
|
||||
hid_host_device_close(hid_device_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hid_host_device_start(hid_device_handle) != ESP_OK)
|
||||
{
|
||||
printf("hid_host_device_start failed\r\n");
|
||||
hid_host_device_close(hid_device_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
usb_mouse_dev = hid_device_handle;
|
||||
usb_mouse_abs_x = 0;
|
||||
usb_mouse_abs_y = 0;
|
||||
|
||||
printf("usb mouse connected\r\n");
|
||||
}
|
||||
|
||||
void usb_mouse_device_callback(hid_host_device_handle_t hid_device_handle, hid_host_driver_event_t event, void *arg)
|
||||
{
|
||||
usb_mouse_evt_t evt = {};
|
||||
evt.event_group = USB_MOUSE_EVT_HID;
|
||||
evt.hid.handle = hid_device_handle;
|
||||
evt.hid.event = event;
|
||||
evt.hid.arg = arg;
|
||||
|
||||
if (usb_mouse_evt_queue)
|
||||
xQueueSend(usb_mouse_evt_queue, &evt, 0);
|
||||
}
|
||||
|
||||
void usb_mouse_lib_task(void *arg)
|
||||
{
|
||||
const usb_host_config_t host_config =
|
||||
{
|
||||
.skip_phy_setup = false,
|
||||
.intr_flags = ESP_INTR_FLAG_LOWMED,
|
||||
};
|
||||
|
||||
esp_err_t err = usb_host_install(&host_config);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
printf("usb_host_install failed: %s\r\n", esp_err_to_name(err));
|
||||
usb_mouse_lib_task_handle = NULL;
|
||||
xTaskNotifyGive((TaskHandle_t)arg);
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
xTaskNotifyGive((TaskHandle_t)arg);
|
||||
|
||||
while (1)
|
||||
{
|
||||
uint32_t event_flags = 0;
|
||||
err = usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
printf("usb_host_lib_handle_events failed: %s\r\n", esp_err_to_name(err));
|
||||
break;
|
||||
}
|
||||
|
||||
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS)
|
||||
{
|
||||
usb_host_device_free_all();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
usb_host_uninstall();
|
||||
usb_mouse_lib_task_handle = NULL;
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void usb_mouse_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
usb_mouse_evt_t evt;
|
||||
esp_err_t err;
|
||||
|
||||
usb_mouse_evt_queue = xQueueCreateWithCaps(8, sizeof(usb_mouse_evt_t), task_ram_type_non_critical);
|
||||
if (!usb_mouse_evt_queue)
|
||||
{
|
||||
printf("usb mouse queue create failed\r\n");
|
||||
usb_mouse_mode_active = false;
|
||||
usb_mouse_task_handle = NULL;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (xTaskCreatePinnedToCoreWithCaps(usb_mouse_lib_task, "usb_mouse_lib", 2048, xTaskGetCurrentTaskHandle(), USB_MOUSE_LIB_TASK_PRIO, &usb_mouse_lib_task_handle, 0, task_ram_type_critical) != pdTRUE)
|
||||
{
|
||||
printf("usb mouse lib task create failed\r\n");
|
||||
vQueueDeleteWithCaps(usb_mouse_evt_queue);
|
||||
usb_mouse_evt_queue = NULL;
|
||||
usb_mouse_mode_active = false;
|
||||
usb_mouse_task_handle = NULL;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(2000));
|
||||
|
||||
const hid_host_driver_config_t hid_cfg =
|
||||
{
|
||||
.create_background_task = true,
|
||||
.task_priority = USB_MOUSE_HID_TASK_PRIO,
|
||||
.stack_size = 4096,
|
||||
.core_id = 0,
|
||||
.callback = usb_mouse_device_callback,
|
||||
.callback_arg = NULL,
|
||||
};
|
||||
|
||||
err = hid_host_install(&hid_cfg);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
printf("hid_host_install failed: %s\r\n", esp_err_to_name(err));
|
||||
vQueueDeleteWithCaps(usb_mouse_evt_queue);
|
||||
usb_mouse_evt_queue = NULL;
|
||||
usb_mouse_mode_active = false;
|
||||
usb_mouse_task_handle = NULL;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("USB switched to host mode, connect mouse\r\n");
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (!xQueueReceive(usb_mouse_evt_queue, &evt, portMAX_DELAY))
|
||||
continue;
|
||||
|
||||
if (evt.event_group == USB_MOUSE_EVT_QUIT)
|
||||
break;
|
||||
|
||||
if (evt.event_group == USB_MOUSE_EVT_HID)
|
||||
usb_mouse_process_device_event(evt.hid.handle, evt.hid.event, evt.hid.arg);
|
||||
}
|
||||
|
||||
if (usb_mouse_dev)
|
||||
{
|
||||
hid_host_device_stop(usb_mouse_dev);
|
||||
hid_host_device_close(usb_mouse_dev);
|
||||
usb_mouse_dev = NULL;
|
||||
}
|
||||
|
||||
hid_host_uninstall();
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
if (usb_mouse_lib_task_handle == NULL)
|
||||
break;
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
|
||||
QueueHandle_t q = usb_mouse_evt_queue;
|
||||
usb_mouse_evt_queue = NULL;
|
||||
if (q) vQueueDeleteWithCaps(q);
|
||||
|
||||
usb_mouse_mode_active = false;
|
||||
usb_mouse_task_handle = NULL;
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
extern "C" esp_err_t usb_mouse_start()
|
||||
{
|
||||
if (usb_mouse_mode_active)
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
|
||||
usb_mouse_mode_active = true;
|
||||
|
||||
if (xTaskCreatePinnedToCoreWithCaps(usb_mouse_task, "usb_mouse", 4096, NULL, USB_MOUSE_TASK_PRIO, &usb_mouse_task_handle, 0, task_ram_type_non_critical) != pdTRUE)
|
||||
{
|
||||
usb_mouse_mode_active = false;
|
||||
usb_mouse_task_handle = NULL;
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
int usb_cmd(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2)
|
||||
{
|
||||
printf("Usage:\r\n");
|
||||
printf(" usb en <0|1>\r\n");
|
||||
printf(" usb up\r\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(argv[1], "up"))
|
||||
{
|
||||
if (argc != 2)
|
||||
{
|
||||
printf("Usage: usb up\r\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
esp_err_t err = usb_mouse_start();
|
||||
if (err == ESP_ERR_INVALID_STATE)
|
||||
{
|
||||
printf("usb mouse mode already active\r\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
printf("usb mouse task create failed: %s\r\n", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("usb mouse host started until reset (NVS unchanged)\r\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "en"))
|
||||
{
|
||||
printf("Unknown subcommand: %s\r\n", argv[1]);
|
||||
printf("Usage:\r\n");
|
||||
printf(" usb en <0|1>\r\n");
|
||||
printf(" usb up\r\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (argc < 3)
|
||||
{
|
||||
printf("Usage: usb en <0|1>\r\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *endp = NULL;
|
||||
unsigned long en = strtoul(argv[2], &endp, 0);
|
||||
if (!endp || *endp || en > 1)
|
||||
{
|
||||
printf("Bad <0|1>: %s\r\n", argv[2]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
app_params.usb_mode = (uint8_t)en;
|
||||
|
||||
esp_err_t err = app_params_save();
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
printf("app_params_save failed: %s\r\n", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("USB enable: %u\r\n", app_params.usb_mode);
|
||||
|
||||
if (app_params.usb_mode)
|
||||
{
|
||||
err = usb_mouse_start();
|
||||
if (err == ESP_ERR_INVALID_STATE)
|
||||
{
|
||||
printf("usb mouse mode already active\r\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
printf("usb mouse task create failed: %s\r\n", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("usb mouse host started\r\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (usb_mouse_mode_active)
|
||||
printf("USB disable saved, active host mode will stay until reset\r\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
extern "C" esp_err_t usb_mouse_start();
|
||||
int usb_cmd(int argc, char **argv);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
// Phase 1 of the SMB2/3 network-share feature (see project plan): an SMB2/3
|
||||
// server task with a hardcoded fake filesystem, to prove the protocol layer
|
||||
// works before anything is wired to the Z80/Wild Commander side.
|
||||
|
||||
#define VFS_SHARE_NAME_MAX 32
|
||||
extern char vfs_share_name[VFS_SHARE_NAME_MAX + 1];
|
||||
|
||||
void vfs_bridge_init();
|
||||
void smb_server_task(void *arg);
|
||||
|
||||
// Start/stop the SMB2/3 server on demand (ESP_CMD_VFS_SERVER_START/STOP,
|
||||
// dispatched via TASK_VFS_SERVER_START/STOP in helper_task) instead of it
|
||||
// running unconditionally once WiFi comes up - the Z80 side is single-
|
||||
// tasking (it can only ever be driving one plugin at a time), so there is
|
||||
// no point keeping the SMB server (or the other services it pauses -
|
||||
// at_uart_suspend()/tracker_suspend()) active except while the smbvfs.wmf
|
||||
// plugin is actually the thing running.
|
||||
void vfs_bridge_start_server();
|
||||
void vfs_bridge_stop_server();
|
||||
@@ -0,0 +1,124 @@
|
||||
// See vfs_request.h. Bridges vfs_bridge.cpp's SMB2 filesystem callbacks
|
||||
// (running on smb_server_task) to the Z80 side over the SPI-slave
|
||||
// protocol. Shared state is protected by a portMUX spinlock rather than a
|
||||
// FreeRTOS mutex because vfs_spi_poll()/vfs_spi_get_request_data() run in
|
||||
// spi_slave.cpp's IRAM_ATTR ISR-context command dispatch (mirrors
|
||||
// ps2_mouse.cpp's ps2_mouse_lock pattern already used in this codebase).
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "spi_slave.h"
|
||||
#include "vfs_request.h"
|
||||
|
||||
static portMUX_TYPE vfs_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
static SemaphoreHandle_t vfs_call_mtx = NULL; // serializes concurrent vfs_command_sync() callers
|
||||
static QueueHandle_t vfs_ack_queue = NULL; // depth 1, posted to by vfs_spi_submit_result()
|
||||
|
||||
static uint8_t s_req_buf[VFS_REQUEST_MAX];
|
||||
static uint32_t s_req_len = 0;
|
||||
static uint8_t s_req_op = 0;
|
||||
static bool s_req_pending = false;
|
||||
|
||||
static uint8_t s_result_buf[DMA_BUF_SIZE];
|
||||
static uint32_t s_result_len = 0;
|
||||
static uint8_t s_result_status = 0;
|
||||
|
||||
void vfs_request_init()
|
||||
{
|
||||
if (!vfs_call_mtx) vfs_call_mtx = xSemaphoreCreateMutex();
|
||||
if (!vfs_ack_queue) vfs_ack_queue = xQueueCreate(1, sizeof(uint8_t));
|
||||
}
|
||||
|
||||
bool vfs_command_sync(uint8_t op, const uint8_t *req_payload, uint32_t req_len,
|
||||
uint32_t timeout_ms, uint8_t *out_status,
|
||||
const uint8_t **out_data, uint32_t *out_len)
|
||||
{
|
||||
if (req_len > VFS_REQUEST_MAX) return false;
|
||||
|
||||
ESP_LOGI("vfs_req", "sync: op=%u waiting for mutex", (unsigned)op);
|
||||
xSemaphoreTake(vfs_call_mtx, portMAX_DELAY);
|
||||
ESP_LOGI("vfs_req", "sync: op=%u got mutex, queuing request", (unsigned)op);
|
||||
|
||||
xQueueReset(vfs_ack_queue); // drop any stale ack from a previous timed-out call
|
||||
|
||||
portENTER_CRITICAL(&vfs_lock);
|
||||
memcpy(s_req_buf, req_payload, req_len);
|
||||
s_req_len = req_len;
|
||||
s_req_op = op;
|
||||
s_req_pending = true;
|
||||
portEXIT_CRITICAL(&vfs_lock);
|
||||
|
||||
uint8_t dummy;
|
||||
bool got_answer = xQueueReceive(vfs_ack_queue, &dummy, pdMS_TO_TICKS(timeout_ms)) == pdTRUE;
|
||||
ESP_LOGI("vfs_req", "sync: op=%u %s", (unsigned)op, got_answer ? "got answer" : "TIMED OUT");
|
||||
|
||||
if (!got_answer)
|
||||
{
|
||||
// Give up waiting. A late answer from the Z80 side after this point
|
||||
// would otherwise land on whatever the NEXT call queues up - clearing
|
||||
// req_pending here means a late GET_REQUEST_DATA/SUBMIT_RESULT for
|
||||
// THIS request just sees nothing pending / is harmlessly dropped.
|
||||
portENTER_CRITICAL(&vfs_lock);
|
||||
s_req_pending = false;
|
||||
portEXIT_CRITICAL(&vfs_lock);
|
||||
xSemaphoreGive(vfs_call_mtx);
|
||||
return false;
|
||||
}
|
||||
|
||||
portENTER_CRITICAL(&vfs_lock);
|
||||
*out_status = s_result_status;
|
||||
*out_data = s_result_buf;
|
||||
*out_len = s_result_len;
|
||||
portEXIT_CRITICAL(&vfs_lock);
|
||||
|
||||
xSemaphoreGive(vfs_call_mtx);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IRAM_ATTR vfs_spi_poll(uint8_t *out_op, uint32_t *out_size)
|
||||
{
|
||||
portENTER_CRITICAL_ISR(&vfs_lock);
|
||||
bool pending = s_req_pending;
|
||||
if (pending)
|
||||
{
|
||||
*out_op = s_req_op;
|
||||
*out_size = s_req_len;
|
||||
}
|
||||
portEXIT_CRITICAL_ISR(&vfs_lock);
|
||||
return pending;
|
||||
}
|
||||
|
||||
uint32_t IRAM_ATTR vfs_spi_get_request_data(uint8_t *dma_buf, uint32_t max_size)
|
||||
{
|
||||
portENTER_CRITICAL_ISR(&vfs_lock);
|
||||
uint32_t n = s_req_len;
|
||||
if (n > max_size) n = max_size;
|
||||
if (s_req_pending) memcpy(dma_buf, s_req_buf, n);
|
||||
else n = 0;
|
||||
portEXIT_CRITICAL_ISR(&vfs_lock);
|
||||
return n;
|
||||
}
|
||||
|
||||
void IRAM_ATTR vfs_spi_submit_result(uint8_t status, const uint8_t *dma_buf, uint32_t size)
|
||||
{
|
||||
if (size > DMA_BUF_SIZE) size = DMA_BUF_SIZE;
|
||||
|
||||
portENTER_CRITICAL_ISR(&vfs_lock);
|
||||
memcpy(s_result_buf, dma_buf, size);
|
||||
s_result_len = size;
|
||||
s_result_status = status;
|
||||
s_req_pending = false;
|
||||
portEXIT_CRITICAL_ISR(&vfs_lock);
|
||||
|
||||
uint8_t dummy = 1;
|
||||
xQueueSendFromISR(vfs_ack_queue, &dummy, NULL);
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
// SPI-side half of the VFS bridge (see project plan, "Phase 2"). Turns an
|
||||
// SMB2 filesystem callback (from vfs_bridge.cpp) into one request/response
|
||||
// round trip over the existing SPI-slave protocol, answered by whatever is
|
||||
// polling ESP_CMD_VFS_POLL on the Z80 side - a hardcoded test responder for
|
||||
// now (test_spi_esp32), the real Wild Commander plugin once vfs.asm is
|
||||
// ported (Phase 3).
|
||||
//
|
||||
// Wire format (all multi-byte fields little-endian; this is our own
|
||||
// convention for now, not yet matched to vfs.asm's - revisit when porting
|
||||
// it in Phase 3):
|
||||
//
|
||||
// Request (from ESP_CMD_VFS_GET_REQUEST_DATA), path-based ops
|
||||
// (STAT/OPEN/DELETE/MKDIR/RMDIR/READDIR):
|
||||
// u8 op (VFS_OP_*)
|
||||
// u16 path_len
|
||||
// u8 path[path_len] (UTF-8, no NUL terminator)
|
||||
//
|
||||
// Request, handle-based ops:
|
||||
// READ: u8 op, u32 handle, u64 offset, u32 size
|
||||
// WRITE: u8 op, u32 handle, u64 offset, u32 size, u8 data[size]
|
||||
// CLOSE: u8 op, u32 handle
|
||||
//
|
||||
// Result (to ESP_CMD_VFS_SUBMIT_RESULT): status travels in ESP_REG_VFS_STATUS
|
||||
// (0 = success), separate from the DMA payload below, which on error is empty:
|
||||
// STAT: u8 is_dir, u64 size
|
||||
// READDIR: u16 count, then count * { u8 name_len, u8 name[name_len], u8 is_dir, u64 size }
|
||||
// OPEN: u32 handle
|
||||
// READ: u8 data[N] (N inferred from the transfer size)
|
||||
// WRITE: u32 written
|
||||
// CLOSE/DELETE/MKDIR/RMDIR: (empty)
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define VFS_REQUEST_MAX 512 // path-based requests are small; enough headroom
|
||||
// Result buffer is sized to DMA_BUF_SIZE (spi_slave.h, 16384) inside
|
||||
// vfs_request.cpp - not redeclared here to avoid a second source of truth.
|
||||
|
||||
void vfs_request_init();
|
||||
|
||||
// Called from the SMB2 server task (vfs_bridge.cpp) for every filesystem
|
||||
// operation. Builds the request payload, blocks until the Z80 side answers
|
||||
// via ESP_CMD_VFS_SUBMIT_RESULT (or times out), and returns the raw result
|
||||
// bytes. Returns false on timeout (no answer within timeout_ms); *out_data
|
||||
// is only valid until the next call to vfs_command_sync (single shared
|
||||
// buffer, single in-flight request at a time - matches this codebase's
|
||||
// existing single-session philosophy).
|
||||
bool vfs_command_sync(uint8_t op, const uint8_t *req_payload, uint32_t req_len,
|
||||
uint32_t timeout_ms, uint8_t *out_status,
|
||||
const uint8_t **out_data, uint32_t *out_len);
|
||||
|
||||
// ---- Called from spi_slave.cpp's ISR dispatch / DREQ handlers -----------
|
||||
|
||||
// ESP_CMD_VFS_POLL: cheap, answered directly in the ISR.
|
||||
bool vfs_spi_poll(uint8_t *out_op, uint32_t *out_size);
|
||||
|
||||
// DREQ_VFS_REQUEST (prepare_tx_data): copies the pending request's payload
|
||||
// into dma_buf. Returns the number of bytes copied.
|
||||
uint32_t vfs_spi_get_request_data(uint8_t *dma_buf, uint32_t max_size);
|
||||
|
||||
// DREQ_VFS_RESULT (process_rx_data): copies dma_buf into the result buffer
|
||||
// and wakes up the waiting vfs_command_sync() call.
|
||||
void vfs_spi_submit_result(uint8_t status, const uint8_t *dma_buf, uint32_t size);
|
||||
@@ -0,0 +1,243 @@
|
||||
// WEATHER_GET adapted from ZiFi-ESP32-S3-Zero (616bee6); wire parser retained.
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs.h"
|
||||
#include "main.h"
|
||||
#include "esp_spi_defs.h"
|
||||
#include "spi_slave.h"
|
||||
#include "mem_obj.h"
|
||||
#include "wifi.h"
|
||||
#include "weather.h"
|
||||
#include "zifi/weather_parse.hpp"
|
||||
|
||||
namespace {
|
||||
static const char *TAG = "weather";
|
||||
constexpr size_t body_capacity = 4096;
|
||||
char cached_country[3] = {};
|
||||
char cached_zip[32] = {};
|
||||
zifi::GeoResult cached_geo = {};
|
||||
bool have_geo = false;
|
||||
|
||||
constexpr uint32_t geo_cache_magic = 0x314f4547; // "GEO1"
|
||||
struct PersistedGeo {
|
||||
uint32_t magic;
|
||||
char country[3];
|
||||
char zip[32];
|
||||
zifi::GeoResult geo;
|
||||
uint32_t checksum;
|
||||
};
|
||||
|
||||
uint32_t geo_checksum(const PersistedGeo &value)
|
||||
{
|
||||
const auto *bytes = reinterpret_cast<const uint8_t *>(&value);
|
||||
uint32_t hash = 2166136261u;
|
||||
for (size_t i = 0; i < offsetof(PersistedGeo, checksum); ++i) {
|
||||
hash = (hash ^ bytes[i]) * 16777619u;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
bool load_geo_cache(const char *country, const char *zip)
|
||||
{
|
||||
nvs_handle_t handle = 0;
|
||||
if (nvs_open("weather", NVS_READONLY, &handle) != ESP_OK) return false;
|
||||
PersistedGeo saved = {};
|
||||
size_t size = sizeof(saved);
|
||||
esp_err_t result = nvs_get_blob(handle, "geo", &saved, &size);
|
||||
nvs_close(handle);
|
||||
if (result != ESP_OK || size != sizeof(saved) || saved.magic != geo_cache_magic ||
|
||||
saved.checksum != geo_checksum(saved) ||
|
||||
saved.country[sizeof(saved.country) - 1] != 0 ||
|
||||
saved.zip[sizeof(saved.zip) - 1] != 0 ||
|
||||
saved.geo.place[sizeof(saved.geo.place) - 1] != 0 ||
|
||||
strcmp(saved.country, country) || strcmp(saved.zip, zip) ||
|
||||
!std::isfinite(saved.geo.latitude) || !std::isfinite(saved.geo.longitude) ||
|
||||
saved.geo.latitude < -90.0f || saved.geo.latitude > 90.0f ||
|
||||
saved.geo.longitude < -180.0f || saved.geo.longitude > 180.0f) return false;
|
||||
cached_geo = saved.geo;
|
||||
strcpy(cached_country, saved.country);
|
||||
strcpy(cached_zip, saved.zip);
|
||||
have_geo = true;
|
||||
ESP_LOGI(TAG, "using saved coordinates for %s/%s", country, zip);
|
||||
return true;
|
||||
}
|
||||
|
||||
void save_geo_cache(const char *country, const char *zip)
|
||||
{
|
||||
PersistedGeo saved = {};
|
||||
saved.magic = geo_cache_magic;
|
||||
strlcpy(saved.country, country, sizeof(saved.country));
|
||||
strlcpy(saved.zip, zip, sizeof(saved.zip));
|
||||
saved.geo = cached_geo;
|
||||
saved.checksum = geo_checksum(saved);
|
||||
nvs_handle_t handle = 0;
|
||||
esp_err_t result = nvs_open("weather", NVS_READWRITE, &handle);
|
||||
if (result == ESP_OK) {
|
||||
result = nvs_set_blob(handle, "geo", &saved, sizeof(saved));
|
||||
if (result == ESP_OK) result = nvs_commit(handle);
|
||||
nvs_close(handle);
|
||||
}
|
||||
if (result != ESP_OK) ESP_LOGW(TAG, "cannot save coordinates: %s", esp_err_to_name(result));
|
||||
}
|
||||
|
||||
// Each attempt has a 15s I/O timeout and body deadline. New attempts may
|
||||
// only start during the first 30s of the command, including after backoff.
|
||||
bool download(const char *url, char *body, size_t &length, int64_t deadline,
|
||||
char *error, size_t error_size)
|
||||
{
|
||||
for (int attempt = 0; attempt < 3; ++attempt) {
|
||||
if (esp_timer_get_time() >= deadline) {
|
||||
snprintf(error, error_size, "timeout");
|
||||
return false;
|
||||
}
|
||||
esp_http_client_config_t cfg = {};
|
||||
cfg.url = url;
|
||||
cfg.timeout_ms = 15000;
|
||||
cfg.disable_auto_redirect = true;
|
||||
cfg.user_agent = "ZX-Evo-weather/1";
|
||||
auto client = esp_http_client_init(&cfg);
|
||||
if (!client) {
|
||||
snprintf(error, error_size, "no memory");
|
||||
return false;
|
||||
}
|
||||
length = 0;
|
||||
bool ok = false, retry = true;
|
||||
ESP_LOGI(TAG, "GET attempt %d: %s", attempt + 1, url);
|
||||
snprintf(error, error_size, "connection failed");
|
||||
esp_err_t open_result = esp_http_client_open(client, 0);
|
||||
if (open_result == ESP_OK && esp_http_client_fetch_headers(client) >= 0) {
|
||||
int status = esp_http_client_get_status_code(client);
|
||||
ESP_LOGI(TAG, "HTTP %d from %s", status, url);
|
||||
if (status < 200 || status >= 300) {
|
||||
snprintf(error, error_size, "http %d", status);
|
||||
retry = status == 429 || status >= 500;
|
||||
} else if (esp_http_client_get_content_length(client) >= body_capacity) {
|
||||
snprintf(error, error_size, "reply too long");
|
||||
retry = false;
|
||||
} else {
|
||||
const int64_t body_deadline = esp_timer_get_time() + 15000000;
|
||||
snprintf(error, error_size, "incomplete reply");
|
||||
while (esp_timer_get_time() < body_deadline) {
|
||||
int n = esp_http_client_read(client, body + length, body_capacity - 1 - length);
|
||||
if (n > 0) length += n;
|
||||
if (esp_http_client_is_complete_data_received(client)) {
|
||||
ok = length != 0;
|
||||
break;
|
||||
}
|
||||
if (length == body_capacity - 1) {
|
||||
snprintf(error, error_size, "reply too long");
|
||||
retry = false;
|
||||
break;
|
||||
}
|
||||
if (n == -ESP_ERR_HTTP_EAGAIN) { vTaskDelay(1); continue; }
|
||||
if (n <= 0) break;
|
||||
}
|
||||
}
|
||||
} else if (open_result != ESP_OK) {
|
||||
snprintf(error, error_size, "connect: %s", esp_err_to_name(open_result));
|
||||
ESP_LOGW(TAG, "open failed for %s: %s", url, esp_err_to_name(open_result));
|
||||
} else {
|
||||
snprintf(error, error_size, "header failed");
|
||||
ESP_LOGW(TAG, "headers failed for %s", url);
|
||||
}
|
||||
esp_http_client_cleanup(client);
|
||||
body[length] = 0;
|
||||
if (ok) return true;
|
||||
if (!retry || attempt == 2) return false;
|
||||
ESP_LOGW(TAG, "retrying after: %s", error);
|
||||
vTaskDelay(pdMS_TO_TICKS(1000 * (attempt + 1)));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void weather_do_get()
|
||||
{
|
||||
const int input = rd_reg8(ESP_REG_OBJ_HANDLE);
|
||||
wr_reg32(ESP_REG_DATA_SIZE, 0);
|
||||
if (!check_handle(input) ||
|
||||
(mem_obj[input].type != OBJ_TYPE_DATA && mem_obj[input].type != OBJ_TYPE_DATAF)) {
|
||||
set_status(ESP_ERR_INV_HANDLE);
|
||||
return;
|
||||
}
|
||||
// Request: two NUL-terminated strings, ISO country code and postal code.
|
||||
const char *data = static_cast<const char *>(mem_obj[input].addr);
|
||||
const size_t size = mem_obj[input].size;
|
||||
if (size < 5 || !data || data[2] != 0 ||
|
||||
!((data[0] >= 'A' && data[0] <= 'Z') || (data[0] >= 'a' && data[0] <= 'z')) ||
|
||||
!((data[1] >= 'A' && data[1] <= 'Z') || (data[1] >= 'a' && data[1] <= 'z'))) {
|
||||
set_status(ESP_ERR_INV_PARAM);
|
||||
return;
|
||||
}
|
||||
size_t zip_len = strnlen(data + 3, size - 3 < sizeof(cached_zip) ? size - 3 : sizeof(cached_zip));
|
||||
if (!zip_len || zip_len >= sizeof(cached_zip) || zip_len >= size - 3) {
|
||||
set_status(ESP_ERR_INV_STR_LEN);
|
||||
return;
|
||||
}
|
||||
char country[3] = {data[0], data[1], 0};
|
||||
char zip[32];
|
||||
memcpy(zip, data + 3, zip_len + 1);
|
||||
if (!wifi_is_connected()) { set_status(ESP_ERR_AP_NOT_CONNECTED); return; }
|
||||
char *body = static_cast<char *>(malloc(body_capacity));
|
||||
if (!body) { set_status(ESP_ERR_OUT_OF_MEMORY); return; }
|
||||
const int64_t deadline = esp_timer_get_time() + 30000000;
|
||||
char url[512], error[64] = {};
|
||||
size_t length = 0;
|
||||
bool ok = true;
|
||||
if (!have_geo || strcmp(country, cached_country) || strcmp(zip, cached_zip)) {
|
||||
have_geo = load_geo_cache(country, zip);
|
||||
}
|
||||
if (!have_geo) {
|
||||
char encoded[94];
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < zip_len; ++i) {
|
||||
unsigned char c = zip[i];
|
||||
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
|
||||
(c >= 'a' && c <= 'z') || c == '-') encoded[j++] = c;
|
||||
else { snprintf(encoded + j, sizeof(encoded) - j, "%%%02X", c); j += 3; }
|
||||
}
|
||||
encoded[j] = 0;
|
||||
snprintf(url, sizeof(url), "http://api.zippopotam.us/%s/%s", country, encoded);
|
||||
ok = download(url, body, length, deadline, error, sizeof(error)) &&
|
||||
zifi::parseZippopotam(body, length, cached_geo, error, sizeof(error));
|
||||
if (ok) {
|
||||
strcpy(cached_country, country);
|
||||
strcpy(cached_zip, zip);
|
||||
have_geo = true;
|
||||
save_geo_cache(country, zip);
|
||||
}
|
||||
}
|
||||
uint8_t record[zifi::kWeatherRecordSize] = {};
|
||||
if (ok) {
|
||||
snprintf(url, sizeof(url),
|
||||
"http://api.open-meteo.com/v1/forecast?latitude=%.4f&longitude=%.4f"
|
||||
"¤t=temperature_2m,weather_code,is_day,wind_speed_10m,precipitation,surface_pressure"
|
||||
"&daily=weather_code,temperature_2m_max,temperature_2m_min,precipitation_sum,sunrise,sunset"
|
||||
"&timezone=auto&forecast_days=6&timeformat=unixtime",
|
||||
static_cast<double>(cached_geo.latitude), static_cast<double>(cached_geo.longitude));
|
||||
ok = download(url, body, length, deadline, error, sizeof(error)) &&
|
||||
zifi::parseOpenMeteo(body, length, cached_geo.place, record, error, sizeof(error));
|
||||
}
|
||||
free(body);
|
||||
if (!ok) {
|
||||
ESP_LOGW(TAG, "%s", error);
|
||||
set_status(ESP_ERR_WEATHER);
|
||||
return;
|
||||
}
|
||||
int output = make_obj(sizeof(record), OBJ_TYPE_DATA);
|
||||
if (output < 0) return; // make_obj publishes the precise allocation error.
|
||||
memcpy(mem_obj[output].addr, record, sizeof(record));
|
||||
wr_reg8(ESP_REG_OBJ_HANDLE, output);
|
||||
wr_reg32(ESP_REG_DATA_OFFSET, 0);
|
||||
wr_reg32(ESP_REG_DATA_SIZE, sizeof(record));
|
||||
set_status(ESP_ST_READY);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
// Called only by the serialized helper task.
|
||||
void weather_do_get();
|
||||
@@ -0,0 +1,723 @@
|
||||
#include "zifi/weather_parse.hpp"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace zifi {
|
||||
namespace {
|
||||
|
||||
// --- крошечный разбор JSON: ровно то, что нужно двум ответам -----------------------
|
||||
// Полного дерева не строим: ищем ключи на верхнем уровне объекта, пропуская
|
||||
// чужие значения целиком. Этого достаточно, потому что структура ответов
|
||||
// известна заранее, а память ESP дорога.
|
||||
|
||||
void setError(char* error, size_t errorSize, const char* text) {
|
||||
if (error != nullptr && errorSize != 0) {
|
||||
snprintf(error, errorSize, "%s", text);
|
||||
}
|
||||
}
|
||||
|
||||
bool isSpace(char c) {
|
||||
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
|
||||
}
|
||||
|
||||
const char* skipSpaces(const char* p, const char* end) {
|
||||
while (p < end && isSpace(*p)) {
|
||||
++p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
// p указывает на открывающую кавычку; вернуть позицию за закрывающей.
|
||||
const char* skipString(const char* p, const char* end) {
|
||||
++p;
|
||||
while (p < end) {
|
||||
if (*p == '\\') {
|
||||
p += 2;
|
||||
continue;
|
||||
}
|
||||
if (*p == '"') {
|
||||
return p + 1;
|
||||
}
|
||||
++p;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Пропустить любое значение: объект, массив, строку, число или литерал.
|
||||
const char* skipValue(const char* p, const char* end) {
|
||||
p = skipSpaces(p, end);
|
||||
if (p >= end) {
|
||||
return nullptr;
|
||||
}
|
||||
if (*p == '"') {
|
||||
return skipString(p, end);
|
||||
}
|
||||
if (*p == '{' || *p == '[') {
|
||||
int depth = 0;
|
||||
while (p < end) {
|
||||
if (*p == '"') {
|
||||
p = skipString(p, end);
|
||||
if (p == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (*p == '{' || *p == '[') {
|
||||
++depth;
|
||||
} else if (*p == '}' || *p == ']') {
|
||||
--depth;
|
||||
if (depth == 0) {
|
||||
return p + 1;
|
||||
}
|
||||
}
|
||||
++p;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
while (p < end && *p != ',' && *p != '}' && *p != ']' && !isSpace(*p)) {
|
||||
++p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
// Найти ключ на верхнем уровне объекта [begin, end) и вернуть указатель на
|
||||
// его значение; nullptr — ключа нет.
|
||||
const char* findKey(const char* begin, const char* end, const char* key) {
|
||||
const size_t keyLength = strlen(key);
|
||||
const char* p = skipSpaces(begin, end);
|
||||
if (p < end && *p == '{') {
|
||||
++p;
|
||||
}
|
||||
while (true) {
|
||||
p = skipSpaces(p, end);
|
||||
if (p >= end || *p != '"') {
|
||||
return nullptr;
|
||||
}
|
||||
const char* nameEnd = skipString(p, end);
|
||||
if (nameEnd == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
const bool match = static_cast<size_t>(nameEnd - p) == keyLength + 2 &&
|
||||
memcmp(p + 1, key, keyLength) == 0;
|
||||
p = skipSpaces(nameEnd, end);
|
||||
if (p >= end || *p != ':') {
|
||||
return nullptr;
|
||||
}
|
||||
p = skipSpaces(p + 1, end);
|
||||
if (match) {
|
||||
return p;
|
||||
}
|
||||
p = skipValue(p, end);
|
||||
if (p == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
p = skipSpaces(p, end);
|
||||
if (p < end && *p == ',') {
|
||||
++p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Число (возможно в кавычках, как lat/lon у Nominatim); null считается нулём.
|
||||
bool parseNumber(const char* p, const char* end, double& out, const char** next) {
|
||||
p = skipSpaces(p, end);
|
||||
if (p >= end) {
|
||||
return false;
|
||||
}
|
||||
bool quoted = false;
|
||||
if (*p == '"') {
|
||||
quoted = true;
|
||||
++p;
|
||||
}
|
||||
if (end - p >= 4 && memcmp(p, "null", 4) == 0) {
|
||||
out = 0.0;
|
||||
p += 4;
|
||||
} else {
|
||||
char* stop = nullptr;
|
||||
out = strtod(p, &stop);
|
||||
if (stop == p || stop > end) {
|
||||
return false;
|
||||
}
|
||||
p = stop;
|
||||
}
|
||||
if (quoted) {
|
||||
if (p >= end || *p != '"') {
|
||||
return false;
|
||||
}
|
||||
++p;
|
||||
}
|
||||
if (next != nullptr) {
|
||||
*next = p;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void appendUtf8(char* out, size_t capacity, size_t& length, uint32_t code) {
|
||||
char buffer[4];
|
||||
size_t count;
|
||||
if (code < 0x80) {
|
||||
buffer[0] = static_cast<char>(code);
|
||||
count = 1;
|
||||
} else if (code < 0x800) {
|
||||
buffer[0] = static_cast<char>(0xC0 | (code >> 6));
|
||||
buffer[1] = static_cast<char>(0x80 | (code & 0x3F));
|
||||
count = 2;
|
||||
} else {
|
||||
buffer[0] = static_cast<char>(0xE0 | (code >> 12));
|
||||
buffer[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
|
||||
buffer[2] = static_cast<char>(0x80 | (code & 0x3F));
|
||||
count = 3;
|
||||
}
|
||||
for (size_t i = 0; i < count && length + 1 < capacity; ++i) {
|
||||
out[length++] = buffer[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Строка в кавычках с раскодированием \", \\, \/ и \uXXXX (в UTF-8).
|
||||
bool parseString(const char* p, const char* end, char* out, size_t capacity) {
|
||||
p = skipSpaces(p, end);
|
||||
if (p >= end || *p != '"' || capacity == 0) {
|
||||
return false;
|
||||
}
|
||||
++p;
|
||||
size_t length = 0;
|
||||
while (p < end && *p != '"') {
|
||||
char c = *p++;
|
||||
if (c == '\\' && p < end) {
|
||||
c = *p++;
|
||||
if (c == 'u' && end - p >= 4) {
|
||||
char hex[5] = {p[0], p[1], p[2], p[3], 0};
|
||||
appendUtf8(out, capacity, length, static_cast<uint32_t>(strtoul(hex, nullptr, 16)));
|
||||
p += 4;
|
||||
continue;
|
||||
}
|
||||
if (c == 'n' || c == 't' || c == 'r') {
|
||||
c = ' ';
|
||||
}
|
||||
}
|
||||
if (length + 1 < capacity) {
|
||||
out[length++] = c;
|
||||
}
|
||||
}
|
||||
out[length] = 0;
|
||||
return p < end;
|
||||
}
|
||||
|
||||
// Массив чисел -> out[0..max); вернуть число прочитанных элементов.
|
||||
size_t parseNumberArray(const char* p, const char* end, double* out, size_t max) {
|
||||
p = skipSpaces(p, end);
|
||||
if (p >= end || *p != '[') {
|
||||
return 0;
|
||||
}
|
||||
++p;
|
||||
size_t count = 0;
|
||||
while (true) {
|
||||
p = skipSpaces(p, end);
|
||||
if (p >= end || *p == ']') {
|
||||
return count;
|
||||
}
|
||||
double value = 0.0;
|
||||
const char* next = nullptr;
|
||||
if (!parseNumber(p, end, value, &next)) {
|
||||
return count;
|
||||
}
|
||||
if (count < max) {
|
||||
out[count] = value;
|
||||
}
|
||||
++count;
|
||||
p = skipSpaces(next, end);
|
||||
if (p < end && *p == ',') {
|
||||
++p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool objectNumber(const char* begin, const char* end, const char* key, double& out) {
|
||||
const char* value = findKey(begin, end, key);
|
||||
return value != nullptr && parseNumber(value, end, out, nullptr);
|
||||
}
|
||||
|
||||
// --- календарь для unixtime -----------------------------------------------------------
|
||||
// Дата по числу дней от 1970-01-01 (алгоритм Говарда Хиннанта).
|
||||
void civilFromDays(int64_t z, int& year, unsigned& month, unsigned& day) {
|
||||
z += 719468;
|
||||
const int64_t era = (z >= 0 ? z : z - 146096) / 146097;
|
||||
const unsigned doe = static_cast<unsigned>(z - era * 146097);
|
||||
const unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
|
||||
const int64_t y = static_cast<int64_t>(yoe) + era * 400;
|
||||
const unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
|
||||
const unsigned mp = (5 * doy + 2) / 153;
|
||||
day = doy - (153 * mp + 2) / 5 + 1;
|
||||
month = mp < 10 ? mp + 3 : mp - 9;
|
||||
year = static_cast<int>(y + (month <= 2 ? 1 : 0));
|
||||
}
|
||||
|
||||
struct LocalTime {
|
||||
int year;
|
||||
unsigned month;
|
||||
unsigned day;
|
||||
unsigned weekday; // 0 — понедельник
|
||||
unsigned hour;
|
||||
unsigned minute;
|
||||
};
|
||||
|
||||
LocalTime localTime(int64_t unixSeconds, int32_t offsetSeconds) {
|
||||
const int64_t local = unixSeconds + offsetSeconds;
|
||||
int64_t days = local / 86400;
|
||||
int64_t seconds = local - days * 86400;
|
||||
if (seconds < 0) {
|
||||
seconds += 86400;
|
||||
--days;
|
||||
}
|
||||
LocalTime result;
|
||||
civilFromDays(days, result.year, result.month, result.day);
|
||||
// 1970-01-01 — четверг, то есть 3 при нумерации с понедельника.
|
||||
result.weekday = static_cast<unsigned>(((days + 3) % 7 + 7) % 7);
|
||||
result.hour = static_cast<unsigned>(seconds / 3600);
|
||||
result.minute = static_cast<unsigned>((seconds % 3600) / 60);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint8_t clampU8(double value) {
|
||||
const long rounded = lround(value);
|
||||
return static_cast<uint8_t>(rounded < 0 ? 0 : (rounded > 255 ? 255 : rounded));
|
||||
}
|
||||
|
||||
int8_t clampI8(double value) {
|
||||
const long rounded = lround(value);
|
||||
return static_cast<int8_t>(rounded < -128 ? -128 : (rounded > 127 ? 127 : rounded));
|
||||
}
|
||||
|
||||
uint16_t clampU16(double value) {
|
||||
const long rounded = lround(value);
|
||||
return static_cast<uint16_t>(rounded < 0 ? 0 : (rounded > 65535 ? 65535 : rounded));
|
||||
}
|
||||
|
||||
void putU16(uint8_t* at, uint16_t value) {
|
||||
at[0] = static_cast<uint8_t>(value);
|
||||
at[1] = static_cast<uint8_t>(value >> 8);
|
||||
}
|
||||
|
||||
// Латиница с диакритикой (U+00C0..U+017F) -> базовая буква ASCII.
|
||||
char latinBase(uint32_t code) {
|
||||
static const struct {
|
||||
uint32_t first;
|
||||
uint32_t last;
|
||||
char base;
|
||||
} kRanges[] = {
|
||||
{0xC0, 0xC5, 'A'}, {0xC7, 0xC7, 'C'}, {0xC8, 0xCB, 'E'}, {0xCC, 0xCF, 'I'},
|
||||
{0xD1, 0xD1, 'N'}, {0xD2, 0xD6, 'O'}, {0xD8, 0xD8, 'O'}, {0xD9, 0xDC, 'U'},
|
||||
{0xDD, 0xDD, 'Y'}, {0xDF, 0xDF, 's'}, {0xE0, 0xE5, 'a'}, {0xE7, 0xE7, 'c'},
|
||||
{0xE8, 0xEB, 'e'}, {0xEC, 0xEF, 'i'}, {0xF1, 0xF1, 'n'}, {0xF2, 0xF6, 'o'},
|
||||
{0xF8, 0xF8, 'o'}, {0xF9, 0xFC, 'u'}, {0xFD, 0xFD, 'y'}, {0xFF, 0xFF, 'y'},
|
||||
{0x100, 0x105, 'a'}, {0x106, 0x10D, 'c'}, {0x10E, 0x111, 'd'}, {0x112, 0x11B, 'e'},
|
||||
{0x11C, 0x123, 'g'}, {0x124, 0x127, 'h'}, {0x128, 0x131, 'i'}, {0x134, 0x135, 'j'},
|
||||
{0x136, 0x138, 'k'}, {0x139, 0x142, 'l'}, {0x143, 0x149, 'n'}, {0x14C, 0x151, 'o'},
|
||||
{0x154, 0x159, 'r'}, {0x15A, 0x161, 's'}, {0x162, 0x167, 't'}, {0x168, 0x173, 'u'},
|
||||
{0x174, 0x175, 'w'}, {0x176, 0x178, 'y'}, {0x179, 0x17E, 'z'},
|
||||
};
|
||||
for (const auto& range : kRanges) {
|
||||
if (code >= range.first && code <= range.last) {
|
||||
return range.base;
|
||||
}
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
|
||||
// Знаки вне А..я и Ё ё, у которых есть замена в CP866; 0 — замены нет.
|
||||
// Украинские и белорусские Є є Ї ї Ў ў в CP866 есть (#F2..#F7). І і там
|
||||
// нет — пишем латинские I i, они выглядят так же; Ґ ґ заменяем на Г г.
|
||||
// Типографские апостроф, тире и кавычки бывают в названиях OpenStreetMap
|
||||
// («Кам’янець-Подільський») — их заменяют знаки ASCII.
|
||||
char cp866Extra(uint32_t code) {
|
||||
static const struct {
|
||||
uint16_t code;
|
||||
uint8_t cp866;
|
||||
} kExtra[] = {
|
||||
{0x404, 0xF2}, {0x454, 0xF3}, {0x407, 0xF4}, {0x457, 0xF5}, // Є є Ї ї
|
||||
{0x40E, 0xF6}, {0x45E, 0xF7}, // Ў ў
|
||||
{0x406, 'I'}, {0x456, 'i'}, {0x490, 0x83}, {0x491, 0xA3}, // І і Ґ ґ
|
||||
{0xB0, 0xF8}, // °
|
||||
{0xA0, ' '}, // неразрывный пробел
|
||||
{0x2BC, '\''}, {0x2018, '\''}, {0x2019, '\''}, // апострофы
|
||||
{0x2013, '-'}, {0x2014, '-'}, // тире
|
||||
{0xAB, '"'}, {0xBB, '"'}, {0x201C, '"'}, {0x201D, '"'}, {0x201E, '"'},
|
||||
};
|
||||
for (const auto& extra : kExtra) {
|
||||
if (code == extra.code) {
|
||||
return static_cast<char>(extra.cp866);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- кодировки значений zifi.ini ----------------------------------------------------------
|
||||
// Буква кириллицы по коду CP866 (#80..#FF); 0 — не буква (псевдографика, знаки).
|
||||
uint32_t cp866Letter(uint8_t code) {
|
||||
static const uint16_t kF0[] = {0x401, 0x451, 0x404, 0x454, 0x407, 0x457, 0x40E, 0x45E};
|
||||
if (code >= 0x80 && code <= 0xAF) {
|
||||
return 0x410 + (code - 0x80); // А..п
|
||||
}
|
||||
if (code >= 0xE0 && code <= 0xEF) {
|
||||
return 0x440 + (code - 0xE0); // р..я
|
||||
}
|
||||
if (code >= 0xF0 && code <= 0xF7) {
|
||||
return kF0[code - 0xF0]; // Ё ё Є є Ї ї Ў ў
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Буква кириллицы по коду CP1251 (#80..#FF); 0 — не буква.
|
||||
uint32_t cp1251Letter(uint8_t code) {
|
||||
static const struct {
|
||||
uint8_t code;
|
||||
uint16_t letter;
|
||||
} kExtra[] = {
|
||||
{0xA8, 0x401}, {0xB8, 0x451}, {0xAA, 0x404}, {0xBA, 0x454}, // Ё ё Є є
|
||||
{0xAF, 0x407}, {0xBF, 0x457}, {0xB2, 0x406}, {0xB3, 0x456}, // Ї ї І і
|
||||
{0xA5, 0x490}, {0xB4, 0x491}, {0xA1, 0x40E}, {0xA2, 0x45E}, // Ґ ґ Ў ў
|
||||
};
|
||||
if (code >= 0xC0) {
|
||||
return 0x410 + (code - 0xC0); // А..я
|
||||
}
|
||||
for (const auto& extra : kExtra) {
|
||||
if (code == extra.code) {
|
||||
return extra.letter;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Длина правильной последовательности UTF-8 с первого байта p (1..4);
|
||||
// 0 — это не UTF-8 (одиночный байт продолжения, обрыв, «длинная» запись).
|
||||
size_t utf8SequenceLength(const unsigned char* p) {
|
||||
if (p[0] < 0x80) {
|
||||
return 1;
|
||||
}
|
||||
size_t count;
|
||||
if (p[0] >= 0xC2 && p[0] <= 0xDF) {
|
||||
count = 2;
|
||||
} else if (p[0] >= 0xE0 && p[0] <= 0xEF) {
|
||||
count = 3;
|
||||
} else if (p[0] >= 0xF0 && p[0] <= 0xF4) {
|
||||
count = 4;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
for (size_t i = 1; i < count; ++i) {
|
||||
if ((p[i] & 0xC0) != 0x80) {
|
||||
return 0; // в том числе конец строки
|
||||
}
|
||||
}
|
||||
if ((p[0] == 0xE0 && p[1] < 0xA0) || (p[0] == 0xED && p[1] >= 0xA0) ||
|
||||
(p[0] == 0xF0 && p[1] < 0x90) || (p[0] == 0xF4 && p[1] >= 0x90)) {
|
||||
return 0; // «длинная» запись или суррогат
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void utf8ToCp866(const char* text, char* out, size_t capacity) {
|
||||
if (capacity == 0) {
|
||||
return;
|
||||
}
|
||||
size_t length = 0;
|
||||
const unsigned char* p = reinterpret_cast<const unsigned char*>(text);
|
||||
while (*p != 0 && length + 1 < capacity) {
|
||||
uint32_t code;
|
||||
if (*p < 0x80) {
|
||||
code = *p++;
|
||||
} else if ((*p & 0xE0) == 0xC0 && (p[1] & 0xC0) == 0x80) {
|
||||
code = ((*p & 0x1F) << 6) | (p[1] & 0x3F);
|
||||
p += 2;
|
||||
} else if ((*p & 0xF0) == 0xE0 && (p[1] & 0xC0) == 0x80 && (p[2] & 0xC0) == 0x80) {
|
||||
code = ((*p & 0x0F) << 12) | ((p[1] & 0x3F) << 6) | (p[2] & 0x3F);
|
||||
p += 3;
|
||||
} else {
|
||||
code = '?';
|
||||
++p;
|
||||
while ((*p & 0xC0) == 0x80) {
|
||||
++p;
|
||||
}
|
||||
}
|
||||
char c;
|
||||
if (code < 0x80) {
|
||||
c = static_cast<char>(code);
|
||||
} else if (code >= 0x410 && code <= 0x43F) {
|
||||
c = static_cast<char>(0x80 + (code - 0x410)); // А..п
|
||||
} else if (code >= 0x440 && code <= 0x44F) {
|
||||
c = static_cast<char>(0xE0 + (code - 0x440)); // р..я
|
||||
} else if (code == 0x401) {
|
||||
c = static_cast<char>(0xF0); // Ё
|
||||
} else if (code == 0x451) {
|
||||
c = static_cast<char>(0xF1); // ё
|
||||
} else {
|
||||
c = cp866Extra(code);
|
||||
if (c == 0) {
|
||||
c = latinBase(code);
|
||||
}
|
||||
}
|
||||
out[length++] = c;
|
||||
}
|
||||
out[length] = 0;
|
||||
}
|
||||
|
||||
void iniTextToUtf8(const char* text, char* out, size_t capacity) {
|
||||
if (capacity == 0) {
|
||||
return;
|
||||
}
|
||||
const unsigned char* src = reinterpret_cast<const unsigned char*>(text);
|
||||
size_t length = 0;
|
||||
// Уже UTF-8 (или чистый ASCII) — копируем целыми буквами.
|
||||
bool utf8 = true;
|
||||
for (const unsigned char* p = src; *p != 0;) {
|
||||
const size_t count = utf8SequenceLength(p);
|
||||
if (count == 0) {
|
||||
utf8 = false;
|
||||
break;
|
||||
}
|
||||
p += count;
|
||||
}
|
||||
if (utf8) {
|
||||
for (const unsigned char* p = src; *p != 0;) {
|
||||
const size_t count = utf8SequenceLength(p);
|
||||
if (length + count >= capacity) {
|
||||
break;
|
||||
}
|
||||
memcpy(out + length, p, count);
|
||||
length += count;
|
||||
p += count;
|
||||
}
|
||||
out[length] = 0;
|
||||
return;
|
||||
}
|
||||
// Однобайтовая кириллица: CP866 или CP1251 — какая даёт больше букв.
|
||||
// В CP866 прописные CP1251 (#C0..#DF) — псевдографика, а в CP1251 почти
|
||||
// все буквы CP866 #80..#AF — знаки, поэтому ошибиться трудно. Поровну —
|
||||
// CP866, родная кодировка Спектрума.
|
||||
size_t letters866 = 0;
|
||||
size_t letters1251 = 0;
|
||||
for (const unsigned char* p = src; *p != 0; ++p) {
|
||||
if (*p >= 0x80) {
|
||||
letters866 += cp866Letter(*p) != 0 ? 1 : 0;
|
||||
letters1251 += cp1251Letter(*p) != 0 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
const bool cp1251 = letters1251 > letters866;
|
||||
for (const unsigned char* p = src; *p != 0; ++p) {
|
||||
uint32_t code = *p;
|
||||
if (code >= 0x80) {
|
||||
code = cp1251 ? cp1251Letter(*p) : cp866Letter(*p);
|
||||
if (code == 0) {
|
||||
code = '?';
|
||||
}
|
||||
}
|
||||
const size_t count = code < 0x80 ? 1 : 2; // кириллица в UTF-8 — два байта
|
||||
if (length + count >= capacity) {
|
||||
break;
|
||||
}
|
||||
appendUtf8(out, capacity, length, code);
|
||||
}
|
||||
out[length] = 0;
|
||||
}
|
||||
|
||||
bool parseCitySearch(const char* json, size_t length, GeoResult& out, bool& notFound,
|
||||
char* error, size_t errorSize) {
|
||||
const char* end = json + length;
|
||||
notFound = false;
|
||||
const char* p = skipSpaces(json, end);
|
||||
if (p >= end || *p != '{') {
|
||||
setError(error, errorSize, "city: broken json");
|
||||
return false;
|
||||
}
|
||||
// Ничего не нашёл — в ответе нет results: {"generationtime_ms":0.4}.
|
||||
const char* results = findKey(json, end, "results");
|
||||
if (results == nullptr) {
|
||||
notFound = true;
|
||||
setError(error, errorSize, "city: not found");
|
||||
return false;
|
||||
}
|
||||
if (*results != '[') {
|
||||
setError(error, errorSize, "city: broken json");
|
||||
return false;
|
||||
}
|
||||
p = skipSpaces(results + 1, end);
|
||||
if (p < end && *p == ']') {
|
||||
notFound = true;
|
||||
setError(error, errorSize, "city: not found");
|
||||
return false;
|
||||
}
|
||||
// count=1: первое место списка — самое крупное из одноимённых.
|
||||
const char* placeEnd = (p < end && *p == '{') ? skipValue(p, end) : nullptr;
|
||||
if (placeEnd == nullptr) {
|
||||
setError(error, errorSize, "city: broken json");
|
||||
return false;
|
||||
}
|
||||
double latitude = 0.0;
|
||||
double longitude = 0.0;
|
||||
if (!objectNumber(p, placeEnd, "latitude", latitude) ||
|
||||
!objectNumber(p, placeEnd, "longitude", longitude)) {
|
||||
setError(error, errorSize, "city: no coordinates");
|
||||
return false;
|
||||
}
|
||||
GeoResult result = {};
|
||||
result.latitude = static_cast<float>(latitude);
|
||||
result.longitude = static_cast<float>(longitude);
|
||||
const char* name = findKey(p, placeEnd, "name");
|
||||
if (name == nullptr || !parseString(name, placeEnd, result.place, sizeof(result.place))) {
|
||||
result.place[0] = 0; // без названия — только погода
|
||||
}
|
||||
out = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseZippopotam(const char* json, size_t length, GeoResult& out,
|
||||
char* error, size_t errorSize) {
|
||||
const char* end = json + length;
|
||||
const char* places = findKey(json, end, "places");
|
||||
if (places == nullptr || *places != '[') {
|
||||
setError(error, errorSize, "zip: no places");
|
||||
return false;
|
||||
}
|
||||
const char* p = places + 1;
|
||||
bool found = false;
|
||||
while (true) {
|
||||
p = skipSpaces(p, end);
|
||||
if (p >= end || *p == ']') {
|
||||
break;
|
||||
}
|
||||
if (*p != '{') {
|
||||
break;
|
||||
}
|
||||
const char* objectEnd = skipValue(p, end);
|
||||
if (objectEnd == nullptr) {
|
||||
break;
|
||||
}
|
||||
GeoResult candidate = {};
|
||||
double latitude = 0.0;
|
||||
double longitude = 0.0;
|
||||
const char* name = findKey(p, objectEnd, "place name");
|
||||
if (objectNumber(p, objectEnd, "latitude", latitude) &&
|
||||
objectNumber(p, objectEnd, "longitude", longitude) &&
|
||||
name != nullptr && parseString(name, objectEnd, candidate.place, sizeof(candidate.place))) {
|
||||
candidate.latitude = static_cast<float>(latitude);
|
||||
candidate.longitude = static_cast<float>(longitude);
|
||||
out = candidate; // остаётся последнее место списка
|
||||
found = true;
|
||||
}
|
||||
p = skipSpaces(objectEnd, end);
|
||||
if (p < end && *p == ',') {
|
||||
++p;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
setError(error, errorSize, "zip: no coordinates");
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
bool parseOpenMeteo(const char* json, size_t length, const char* placeUtf8,
|
||||
uint8_t* record, char* error, size_t errorSize) {
|
||||
const char* end = json + length;
|
||||
memset(record, 0, kWeatherRecordSize);
|
||||
record[kWrVersion] = kWeatherRecordVersion;
|
||||
|
||||
double offset = 0.0;
|
||||
if (!objectNumber(json, end, "utc_offset_seconds", offset)) {
|
||||
setError(error, errorSize, "meteo: no utc offset");
|
||||
return false;
|
||||
}
|
||||
const int32_t offsetSeconds = static_cast<int32_t>(offset);
|
||||
|
||||
const char* current = findKey(json, end, "current");
|
||||
const char* daily = findKey(json, end, "daily");
|
||||
if (current == nullptr || *current != '{' || daily == nullptr || *daily != '{') {
|
||||
setError(error, errorSize, "meteo: no current/daily");
|
||||
return false;
|
||||
}
|
||||
const char* currentEnd = skipValue(current, end);
|
||||
const char* dailyEnd = skipValue(daily, end);
|
||||
if (currentEnd == nullptr || dailyEnd == nullptr) {
|
||||
setError(error, errorSize, "meteo: broken json");
|
||||
return false;
|
||||
}
|
||||
|
||||
double temperature, code, isDay, wind, precipitation, pressure, dataTime;
|
||||
if (!objectNumber(current, currentEnd, "temperature_2m", temperature) ||
|
||||
!objectNumber(current, currentEnd, "weather_code", code) ||
|
||||
!objectNumber(current, currentEnd, "is_day", isDay) ||
|
||||
!objectNumber(current, currentEnd, "wind_speed_10m", wind) ||
|
||||
!objectNumber(current, currentEnd, "precipitation", precipitation) ||
|
||||
!objectNumber(current, currentEnd, "surface_pressure", pressure) ||
|
||||
!objectNumber(current, currentEnd, "time", dataTime)) {
|
||||
setError(error, errorSize, "meteo: current fields");
|
||||
return false;
|
||||
}
|
||||
|
||||
// double, а не float: unixtime ~1,8e9 во float (24 бита мантиссы) теряет
|
||||
// до двух минут — восход и закат уезжали бы на минуту.
|
||||
double times[kWeatherMaxDays], codes[kWeatherMaxDays], tmax[kWeatherMaxDays];
|
||||
double tmin[kWeatherMaxDays], sums[kWeatherMaxDays];
|
||||
double sunrise[kWeatherMaxDays], sunset[kWeatherMaxDays];
|
||||
struct {
|
||||
const char* key;
|
||||
double* out;
|
||||
} arrays[] = {
|
||||
{"time", times}, {"weather_code", codes}, {"temperature_2m_max", tmax},
|
||||
{"temperature_2m_min", tmin}, {"precipitation_sum", sums},
|
||||
{"sunrise", sunrise}, {"sunset", sunset},
|
||||
};
|
||||
size_t count = kWeatherMaxDays;
|
||||
for (const auto& array : arrays) {
|
||||
const char* value = findKey(daily, dailyEnd, array.key);
|
||||
const size_t got = value == nullptr ? 0 : parseNumberArray(value, dailyEnd, array.out,
|
||||
kWeatherMaxDays);
|
||||
if (got == 0) {
|
||||
setError(error, errorSize, "meteo: daily fields");
|
||||
return false;
|
||||
}
|
||||
if (got < count) {
|
||||
count = got;
|
||||
}
|
||||
}
|
||||
|
||||
record[kWrStatus] = 1;
|
||||
utf8ToCp866(placeUtf8 == nullptr ? "" : placeUtf8,
|
||||
reinterpret_cast<char*>(record + kWrPlace), kWeatherPlaceLength);
|
||||
record[kWrTemp] = static_cast<uint8_t>(clampI8(temperature));
|
||||
record[kWrCode] = clampU8(code);
|
||||
record[kWrIsDay] = isDay != 0.0 ? 1 : 0;
|
||||
putU16(record + kWrWind10, clampU16(wind * 10.0));
|
||||
putU16(record + kWrPrecip10, clampU16(precipitation * 10.0));
|
||||
putU16(record + kWrPressure, clampU16(pressure * 0.750062)); // гПа -> мм рт. ст.
|
||||
const LocalTime rise = localTime(static_cast<int64_t>(sunrise[0]), offsetSeconds);
|
||||
const LocalTime set = localTime(static_cast<int64_t>(sunset[0]), offsetSeconds);
|
||||
const LocalTime now = localTime(static_cast<int64_t>(dataTime), offsetSeconds);
|
||||
record[kWrSunrise] = static_cast<uint8_t>(rise.hour);
|
||||
record[kWrSunrise + 1] = static_cast<uint8_t>(rise.minute);
|
||||
record[kWrSunset] = static_cast<uint8_t>(set.hour);
|
||||
record[kWrSunset + 1] = static_cast<uint8_t>(set.minute);
|
||||
record[kWrDataTime] = static_cast<uint8_t>(now.hour);
|
||||
record[kWrDataTime + 1] = static_cast<uint8_t>(now.minute);
|
||||
record[kWrDayCount] = static_cast<uint8_t>(count);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
uint8_t* day = record + kWrDays + i * kWeatherDaySize;
|
||||
// Полдень местного дня: unixtime полуночи плюс смещение уже даёт нужную
|
||||
// дату, а полдень защищает от округления на границе суток.
|
||||
const LocalTime date = localTime(static_cast<int64_t>(times[i]) + 43200, offsetSeconds);
|
||||
day[0] = static_cast<uint8_t>(date.day);
|
||||
day[1] = static_cast<uint8_t>(date.month);
|
||||
day[2] = static_cast<uint8_t>(date.weekday);
|
||||
day[3] = clampU8(codes[i]);
|
||||
day[4] = static_cast<uint8_t>(clampI8(tmin[i]));
|
||||
day[5] = static_cast<uint8_t>(clampI8(tmax[i]));
|
||||
putU16(day + 6, clampU16(sums[i] * 10.0));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace zifi
|
||||
+1527
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define CONNECT_TIMEOUT_MS (10000)
|
||||
#define DEFAULT_SCAN_LIST_SIZE 64
|
||||
|
||||
void initialize_wifi();
|
||||
int wf_scan(int timeout);
|
||||
uint16_t wf_get_ap_num();
|
||||
void wf_get_ap(int idx, uint8_t &auth, int8_t &rssi, uint8_t &chan, uint8_t *&ssid);
|
||||
void get_ip(uint8_t *i, uint8_t *m, uint8_t *g);
|
||||
bool wifi_connect(const char *ssid, const char *pass, int timeout_ms);
|
||||
bool wifi_connect_saved(int timeout_ms);
|
||||
bool wifi_is_enabled();
|
||||
bool wifi_is_connected();
|
||||
bool wifi_has_saved_ap();
|
||||
void wifi_start_autoconnect();
|
||||
void wifi_disconnect_now();
|
||||
|
||||
void esp_console_register_wifi_commands();
|
||||
|
||||
// Shared mutex serializing WiFi radio access between the SPI-slave path
|
||||
// (main/helper.cpp) and the AT-command UART path (main/at_uart.cpp) - there is
|
||||
// only one physical radio, so both interfaces must take this before touching
|
||||
// esp_wifi_*/net.state.
|
||||
extern SemaphoreHandle_t net_mtx;
|
||||
|
||||
bool wifi_connect_locked(const char *ssid, const char *pass, int timeout_ms);
|
||||
void wifi_disconnect_now_locked();
|
||||
int wf_scan_locked(int timeout);
|
||||
|
||||
// SNTP config, backing AT+CIPSNTPCFG / AT+CIPSNTPTIME (main/at_uart.cpp).
|
||||
// timezone is a plain hour offset (-12..14, per ESP-AT), applied on top of the
|
||||
// UTC time SNTP itself provides - not a full POSIX TZ. srv2/srv3 may be NULL.
|
||||
void sntp_apply_config(bool enable, int timezone, const char *srv1, const char *srv2, const char *srv3);
|
||||
void sntp_get_config(bool &enable, int &timezone, char *srv1, char *srv2, char *srv3, size_t buflen);
|
||||
void sntp_format_time(char *buf, size_t buflen);
|
||||
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Разбор ответов погодных сервисов в двоичную запись для Z80.
|
||||
//
|
||||
// Модуль не зависит от Arduino: его собирает и host-тест на ПК
|
||||
// (tests/test_weather_parse.py) на сохранённых ответах серверов.
|
||||
// Формат записи един для прошивки, плагина (Weather screensaver/src/weather.asm)
|
||||
// и эталона (Weather screensaver/tools/design.py); менять только вместе.
|
||||
|
||||
namespace zifi {
|
||||
|
||||
constexpr uint8_t kWeatherRecordVersion = 1;
|
||||
constexpr size_t kWeatherPlaceLength = 24;
|
||||
constexpr size_t kWeatherMaxDays = 6;
|
||||
constexpr size_t kWeatherDaySize = 8;
|
||||
|
||||
// Смещения полей записи.
|
||||
enum WeatherRecordLayout : size_t {
|
||||
kWrStatus = 0, // 1 — данные есть
|
||||
kWrVersion = 1, // kWeatherRecordVersion
|
||||
kWrPlace = 2, // название места, CP866, ноль в конце, 24 байта
|
||||
kWrTemp = 26, // температура, °C, знаковый байт
|
||||
kWrCode = 27, // код погоды WMO
|
||||
kWrIsDay = 28, // 1 — день
|
||||
kWrWind10 = 29, // ветер, км/ч x10, u16
|
||||
kWrPrecip10 = 31, // осадки за час, мм x10, u16
|
||||
kWrPressure = 33, // давление у поверхности, мм рт. ст., u16
|
||||
kWrSunrise = 35, // час, минута
|
||||
kWrSunset = 37, // час, минута
|
||||
kWrDataTime = 39, // местное время данных: час, минута
|
||||
kWrDayCount = 41, // число дневных записей
|
||||
kWrDays = 42, // дневные записи по 8 байт: число, месяц, день недели
|
||||
// (0 — понедельник), код WMO, tmin, tmax, осадки x10 u16
|
||||
};
|
||||
constexpr size_t kWeatherRecordSize = kWrDays + kWeatherDaySize * kWeatherMaxDays;
|
||||
|
||||
struct GeoResult {
|
||||
float latitude;
|
||||
float longitude;
|
||||
char place[64]; // UTF-8, название места для экрана
|
||||
};
|
||||
|
||||
// Ответ геокодера Open-Meteo на поиск по названию
|
||||
// (geocoding-api.open-meteo.com/v1/search?name=…&count=1&language=en|ru):
|
||||
// координаты и название места на языке запроса («Kyiv», «Рим»; у маленьких
|
||||
// мест без перевода — местное название). notFound=true — в ответе
|
||||
// нет results: такого названия геокодер не знает, повтор не поможет.
|
||||
bool parseCitySearch(const char* json, size_t length, GeoResult& out, bool& notFound,
|
||||
char* error, size_t errorSize);
|
||||
|
||||
// Ответ api.zippopotam.us/<страна>/<индекс> (поиск по почтовому индексу,
|
||||
// ключ zip:): координаты и название места (берётся последнее место списка —
|
||||
// для итальянских CAP это коммуна).
|
||||
bool parseZippopotam(const char* json, size_t length, GeoResult& out,
|
||||
char* error, size_t errorSize);
|
||||
|
||||
// Значение из zifi.ini -> UTF-8 (в out, с нулём в конце). Файл могли
|
||||
// сохранить в UTF-8 (редактор на ПК), в CP866 (редактор Wild Commander) или
|
||||
// в CP1251 (Блокнот Windows «ANSI»). Правильный UTF-8 остаётся как есть,
|
||||
// иначе берётся та из CP866 и CP1251, в которой больше кириллических букв.
|
||||
void iniTextToUtf8(const char* text, char* out, size_t capacity);
|
||||
|
||||
// Ответ api.open-meteo.com/v1/forecast (current + daily, timeformat=unixtime,
|
||||
// timezone=auto) -> запись kWeatherRecordSize байт. placeUtf8 — название
|
||||
// места в UTF-8, как его дал справочник; в CP866 оно переводится только
|
||||
// здесь, один раз (повторный перевод CP866 превращал кириллицу в «??»).
|
||||
bool parseOpenMeteo(const char* json, size_t length, const char* placeUtf8,
|
||||
uint8_t* record, char* error, size_t errorSize);
|
||||
|
||||
// UTF-8 -> CP866: кириллица переводится (с украинскими и белорусскими
|
||||
// буквами), латиница с диакритикой упрощается до базовой буквы, типографские
|
||||
// апостроф, тире и кавычки — до знаков ASCII, остальное заменяется '?'.
|
||||
// Строка обрезается по capacity.
|
||||
void utf8ToCp866(const char* text, char* out, size_t capacity);
|
||||
|
||||
} // namespace zifi
|
||||
@@ -0,0 +1,435 @@
|
||||
const u8 zx_screen[6912] =
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0xFF, 0x9F, 0x00, 0xFD, 0xE6,
|
||||
0x00, 0x07, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x3E, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00,
|
||||
0x00, 0xF0, 0x3B, 0xFC, 0x60, 0x80, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0xC7, 0xE0, 0x00, 0x00, 0x07, 0xC0, 0x00, 0x00, 0x30,
|
||||
0x00, 0x02, 0x00, 0x09, 0xC0, 0x2F, 0xF0, 0xC8, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xCD, 0xA0, 0x63, 0xC4, 0x00, 0xFC, 0xD0, 0x0D, 0x40, 0x00, 0x00,
|
||||
0x00, 0xFF, 0x00, 0x18, 0x00, 0x00, 0x4F, 0x8C, 0x06, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x61, 0xC0, 0x40, 0x13, 0xE0, 0xE0, 0x78, 0x2F, 0x81, 0xC2, 0x05, 0xC6,
|
||||
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x08, 0x30, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x0C, 0x00, 0x00, 0x89, 0x03, 0x89, 0x1F, 0x70, 0x00, 0x02, 0xAD, 0xEF, 0xFD, 0xF8,
|
||||
0x10, 0x0E, 0xFF, 0x00, 0x00, 0x40, 0x00, 0x63, 0xBF, 0x80, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x08, 0x23, 0xD9, 0x89, 0xCC, 0x6A, 0x0F, 0x00, 0x02, 0x03, 0xA4, 0x5D, 0xAC,
|
||||
0xFC, 0x00, 0x00, 0x07, 0x00, 0xFF, 0x00, 0x14, 0x07, 0x49, 0xE1, 0x80, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xF7, 0x23, 0x20, 0xD3, 0xC9,
|
||||
0x0C, 0x0C, 0x60, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x78, 0xFC, 0x40, 0x00, 0x00, 0xFF, 0xFF, 0x00,
|
||||
0x00, 0xC0, 0x67, 0xE1, 0x80, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x03, 0x1D, 0x11, 0x00, 0x00, 0x0D, 0xE0, 0x00, 0x06, 0x60,
|
||||
0x00, 0x00, 0x02, 0x07, 0x08, 0x8F, 0xC0, 0xF0, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x04, 0x90, 0xF8, 0x27, 0xC8, 0x40, 0xF0, 0xE0, 0x0A, 0x80, 0x00, 0x00,
|
||||
0x00, 0xFF, 0x00, 0x20, 0x00, 0x00, 0x1E, 0x31, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x82, 0x80, 0x11, 0x26, 0x24, 0xE0, 0x38, 0x1F, 0x11, 0x44, 0x03, 0xC8,
|
||||
0x10, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x01, 0x10, 0x30, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x18, 0x00, 0x30, 0x05, 0x55, 0x82, 0x9F, 0xB0, 0x00, 0x01, 0x5A, 0x5B, 0xFC, 0xA0,
|
||||
0x00, 0x1C, 0xFF, 0x00, 0x00, 0x01, 0x00, 0xC7, 0xFC, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x47, 0x5B, 0x1D, 0xA4, 0x64, 0x8F, 0x40, 0x48, 0x35, 0x10, 0xAB, 0xA0,
|
||||
0xF0, 0x02, 0x00, 0x0E, 0x04, 0xFF, 0x00, 0x0C, 0x43, 0xDA, 0x84, 0x08, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAC, 0x07, 0xC0, 0x83, 0x03,
|
||||
0x38, 0x18, 0xC1, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x81, 0xE0, 0xF7, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0x00,
|
||||
0x01, 0x80, 0x8F, 0x48, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0E, 0x10, 0x00, 0x01, 0x03, 0xC0, 0x00, 0x1F, 0x00,
|
||||
0x00, 0x00, 0x00, 0x0C, 0x10, 0x37, 0x00, 0xE0, 0x08, 0xF8, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xF4, 0x26, 0x89, 0x00, 0xF0, 0xD0, 0x05, 0x40, 0x00, 0x00,
|
||||
0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x78, 0xC1, 0x18, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02, 0x01, 0x06, 0x09, 0x0C, 0x28, 0xA0, 0x78, 0x2F, 0x08, 0xC2, 0x0B, 0xC0,
|
||||
0xC0, 0x00, 0x00, 0x40, 0xFF, 0xFF, 0x02, 0x00, 0x60, 0xEC, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x10, 0x00, 0x71, 0xC5, 0x46, 0x86, 0x9E, 0x70, 0x00, 0x08, 0x00, 0x25, 0xFE, 0x80,
|
||||
0x07, 0x20, 0xFF, 0x00, 0x02, 0x02, 0x00, 0x8A, 0x71, 0x06, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xCE, 0x73, 0x2C, 0xE9, 0xC6, 0x8F, 0x04, 0x01, 0x01, 0x81, 0x11, 0xC0,
|
||||
0xE6, 0x00, 0x00, 0x1C, 0x00, 0xFF, 0x01, 0x0A, 0x46, 0xB0, 0x08, 0x30, 0x40, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x50, 0x04, 0x81, 0x06, 0x01,
|
||||
0x70, 0x30, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x03, 0x02, 0xDE, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0x20,
|
||||
0x02, 0x00, 0x1E, 0x01, 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x7A, 0x22, 0x00, 0x06, 0x07, 0x80, 0x00, 0x78, 0x80,
|
||||
0x00, 0x00, 0x00, 0x10, 0x60, 0xCC, 0x00, 0xC0, 0x01, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xD0, 0x26, 0x4E, 0x10, 0xB0, 0xE0, 0x01, 0xC0, 0x00, 0x01,
|
||||
0x00, 0xFF, 0x40, 0x00, 0x00, 0x01, 0xE2, 0x03, 0x20, 0x01, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0x0C, 0x55, 0x19, 0x59, 0x70, 0x38, 0x05, 0x05, 0x60, 0x17, 0x80,
|
||||
0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x01, 0x41, 0xC1, 0x88, 0x02, 0x80, 0x20, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x20, 0x00, 0xE4, 0xC7, 0xAE, 0x0D, 0xB9, 0x70, 0x00, 0x00, 0x00, 0x00, 0x3D, 0x82,
|
||||
0x0D, 0xE1, 0xFF, 0x00, 0x18, 0x0C, 0x00, 0x05, 0x43, 0x1C, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x9C, 0x6B, 0x1D, 0xA0, 0xC4, 0x0F, 0x00, 0x12, 0x90, 0x28, 0x6B, 0xC0,
|
||||
0xB8, 0x10, 0x00, 0x30, 0x00, 0xFF, 0x00, 0x88, 0x22, 0xB1, 0x13, 0xE0, 0x00, 0x04, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x19, 0x00, 0x08, 0x07, 0x0C, 0x00,
|
||||
0xE0, 0xE0, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x08, 0x0C, 0xB0, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0x00,
|
||||
0x00, 0x00, 0x38, 0x13, 0x38, 0x38, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x9B, 0xC2, 0x00, 0x38, 0x0E, 0x00, 0x00, 0xF0, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xE3, 0x30, 0x00, 0x00, 0x06, 0xF0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x45, 0x4C, 0x41, 0x70, 0xD0, 0x0A, 0xC0, 0x00, 0x00,
|
||||
0x00, 0xFF, 0xE0, 0x00, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, 0x03, 0x1C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x04, 0x21, 0x82, 0x15, 0xF3, 0xD0, 0x30, 0x4A, 0x00, 0xFD, 0x0E, 0xC0,
|
||||
0xB0, 0x00, 0x04, 0x00, 0xFF, 0xFF, 0x03, 0x87, 0x80, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0xC0, 0x97, 0xAF, 0x4B, 0x30, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x06, 0x88,
|
||||
0x17, 0xC0, 0xFF, 0x00, 0x30, 0x18, 0x00, 0x40, 0x46, 0x98, 0x46, 0x70, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x03, 0x30, 0x2B, 0x39, 0x64, 0x80, 0x07, 0x00, 0x81, 0x05, 0x45, 0x81, 0x80,
|
||||
0xF0, 0x40, 0x00, 0xC0, 0x00, 0xFF, 0x08, 0x24, 0x02, 0x69, 0x24, 0xC2, 0x00, 0x1E, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x37, 0x00, 0x01, 0x1F, 0x00, 0x00,
|
||||
0xCB, 0xC0, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x39, 0x00, 0x00, 0x00, 0x60, 0xFF, 0xFF, 0x00,
|
||||
0x00, 0x00, 0x60, 0x01, 0x70, 0x5C, 0x80, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x05, 0x8A, 0x44, 0x00, 0x70, 0x58, 0x00, 0x01, 0xE3, 0x00,
|
||||
0x02, 0x00, 0x00, 0x03, 0xC6, 0x40, 0x03, 0x00, 0x4D, 0xE0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x09, 0x55, 0x5B, 0x83, 0x38, 0xE0, 0x04, 0x80, 0x58, 0x00,
|
||||
0x08, 0xFF, 0x40, 0x00, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x04, 0x06, 0x38, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x80, 0x10, 0x07, 0x22, 0x2C, 0xA6, 0xF0, 0x40, 0x01, 0x0C, 0xDB, 0x87, 0xC3,
|
||||
0xE0, 0x20, 0x30, 0x00, 0xFF, 0xFF, 0x06, 0x0F, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x26, 0xA6, 0xC3, 0x6C, 0xF0, 0x00, 0x02, 0x02, 0x02, 0x01, 0xC0,
|
||||
0x1F, 0x98, 0xFF, 0x00, 0x00, 0x30, 0x00, 0xA2, 0x92, 0x36, 0xEC, 0xE0, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x04, 0x40, 0x2A, 0x33, 0x70, 0xA4, 0x63, 0x00, 0x0A, 0x20, 0xBA, 0x88, 0x80,
|
||||
0xC0, 0x00, 0x02, 0x00, 0x01, 0xFF, 0x00, 0x56, 0x23, 0x53, 0x4C, 0x8C, 0x00, 0x78, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xF0, 0x8F, 0x00, 0x03, 0x7F, 0x20, 0x00,
|
||||
0xD7, 0x81, 0x00, 0x00, 0x38, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x18, 0xE7, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0x80,
|
||||
0x00, 0x00, 0x80, 0x03, 0xC3, 0xFB, 0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xCC, 0x69, 0x90, 0x44, 0x03, 0x08, 0x00, 0x0A, 0x04, 0x76, 0x00,
|
||||
0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x0F, 0x00, 0x17, 0xC0, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0F, 0x06, 0x04, 0x0D, 0x5C, 0x07, 0x70, 0xD0, 0x81, 0xC0, 0x1E, 0x04,
|
||||
0x60, 0xFF, 0x00, 0x00, 0x00, 0x10, 0x00, 0x1F, 0x02, 0x00, 0x0C, 0x30, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x44, 0x0C, 0x18, 0xF0, 0x00, 0x12, 0x2A, 0xAB, 0x4E, 0xCC,
|
||||
0xC3, 0x81, 0xC0, 0x00, 0xFF, 0xFF, 0x11, 0x3C, 0x18, 0x94, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02, 0x11, 0x0E, 0x94, 0x87, 0x59, 0xF0, 0x04, 0x02, 0x23, 0xA8, 0x20, 0xC0,
|
||||
0x3B, 0xC0, 0xFF, 0x00, 0x80, 0x01, 0x00, 0x04, 0x02, 0x56, 0xB9, 0xC0, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x05, 0x22, 0x6B, 0x38, 0xCA, 0xC3, 0x00, 0x01, 0x09, 0x7C, 0x45, 0x00,
|
||||
0x80, 0x00, 0x20, 0x00, 0x00, 0xFF, 0x00, 0x43, 0x22, 0x2F, 0x58, 0x38, 0x01, 0xF0, 0x08, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x04,
|
||||
0x02, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x0F, 0xC0, 0x3F, 0x00, 0x00, 0xFF, 0x00, 0x00,
|
||||
0xBF, 0x87, 0x81, 0x01, 0xF0, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x73, 0xCF, 0x40, 0x00, 0x01, 0xE0, 0xFF, 0xFF, 0x04,
|
||||
0x00, 0x00, 0x00, 0x06, 0x9F, 0xAC, 0x03, 0x82, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x9D, 0xC0, 0x91, 0xC4, 0x08, 0x20, 0x20, 0x3F, 0x80, 0x28, 0x00,
|
||||
0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x37, 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x3F, 0x0F, 0x04, 0x9B, 0x78, 0x0F, 0x38, 0xE0, 0x0A, 0xC4, 0x1F, 0x93,
|
||||
0xC0, 0xFF, 0x00, 0x00, 0x80, 0x00, 0x00, 0x3F, 0x00, 0x19, 0x38, 0x40, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x02, 0x00, 0x40, 0x1E, 0x45, 0x09, 0x78, 0xD0, 0x00, 0x01, 0x15, 0x56, 0xB7, 0xBE,
|
||||
0x07, 0x3F, 0x80, 0x00, 0xFF, 0xFF, 0x03, 0xFA, 0x31, 0x68, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0x16, 0x18, 0xC2, 0x40, 0xF0, 0x02, 0x15, 0x55, 0x55, 0x62, 0x40,
|
||||
0xF7, 0x00, 0xFF, 0x03, 0x00, 0x82, 0x02, 0x14, 0x03, 0x2C, 0xBE, 0xC2, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x10, 0x00, 0x92, 0x6B, 0xB4, 0x13, 0x03, 0x00, 0x44, 0x03, 0xFE, 0x80, 0x00,
|
||||
0x00, 0x00, 0x06, 0x00, 0x08, 0xFF, 0x02, 0x32, 0x15, 0x1A, 0x48, 0x70, 0x03, 0xE0, 0x20, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x6B, 0xB8, 0x80, 0x09, 0x00, 0x09, 0x12, 0x00, 0x59, 0x01,
|
||||
0x00, 0xF7, 0xE3, 0x00, 0x90, 0x00, 0x00, 0x0C, 0x14, 0xC6, 0xB1, 0xE4, 0x0F, 0xC0, 0xE0, 0x00,
|
||||
0x00, 0x01, 0xFE, 0x07, 0xD4, 0x11, 0x11, 0xE1, 0x58, 0x7C, 0x00, 0x09, 0x03, 0x0A, 0x12, 0x78,
|
||||
0x38, 0xC0, 0xE0, 0x1C, 0xEE, 0x10, 0x00, 0x15, 0x29, 0xCA, 0x58, 0x70, 0x04, 0x04, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x0F, 0x19, 0xE1, 0xC4, 0x86, 0x20, 0x18, 0x00, 0x13, 0x05, 0x5B, 0x18, 0x00,
|
||||
0x80, 0x01, 0xF0, 0x60, 0x18, 0x00, 0x10, 0x8A, 0x25, 0x84, 0x81, 0x04, 0x10, 0x0C, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x90, 0xD0, 0x86, 0x84, 0xB8, 0x78, 0x00, 0x09, 0x92, 0x00, 0x40, 0x80,
|
||||
0x16, 0x00, 0x80, 0x5E, 0x80, 0x00, 0x72, 0x92, 0x11, 0xEC, 0x63, 0x91, 0x40, 0x0C, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x04, 0x18, 0x00, 0x09, 0x3A, 0x50, 0x00, 0x13, 0x47, 0xFF, 0x34, 0xC7,
|
||||
0x06, 0xF0, 0x6E, 0x01, 0x01, 0x80, 0x00, 0x8A, 0x48, 0xA1, 0x64, 0x29, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x18, 0x00, 0x02, 0x02, 0x43, 0x6C, 0xFD, 0xC0, 0x00, 0x09, 0x03, 0x63, 0x9A, 0xC1,
|
||||
0x01, 0xC0, 0x00, 0x20, 0x0C, 0x0D, 0x00, 0xC9, 0x12, 0xA4, 0xC6, 0x20, 0xC8, 0x09, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x16, 0x00, 0x18, 0x03, 0x13, 0x68, 0xC0, 0x00, 0x09, 0x84, 0x82, 0x07, 0x9B,
|
||||
0x00, 0xC0, 0x80, 0x00, 0x80, 0x73, 0x00, 0x1C, 0x19, 0x2E, 0xC8, 0x89, 0x26, 0x33, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x86, 0x00, 0x02, 0x9F, 0x00, 0x00, 0x00, 0x02, 0x78, 0x80, 0xA2, 0x0C,
|
||||
0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x82, 0x00, 0xDD, 0x05, 0xC2, 0xC6, 0xF0, 0xE0, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x20, 0x00, 0xD8, 0x4B, 0x31, 0xD0, 0x73, 0x00, 0x02, 0x06, 0x00, 0x8C, 0x87,
|
||||
0x00, 0xCF, 0x87, 0x01, 0x30, 0x02, 0x00, 0x82, 0x09, 0x44, 0xE0, 0xC0, 0x1F, 0xC1, 0xC0, 0x00,
|
||||
0x00, 0x03, 0x3C, 0x0E, 0x64, 0x04, 0x98, 0xCD, 0x50, 0x7C, 0x00, 0x23, 0x14, 0xF7, 0x24, 0x40,
|
||||
0x70, 0x72, 0x08, 0x70, 0xBC, 0x00, 0x11, 0x05, 0x95, 0xAA, 0xB2, 0x80, 0x10, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x1D, 0xCB, 0x70, 0x60, 0x0A, 0x41, 0x3C, 0x00, 0x07, 0x12, 0xED, 0x1A, 0x87,
|
||||
0x0C, 0x66, 0xE0, 0x90, 0x30, 0x00, 0x84, 0x4A, 0x4D, 0x94, 0x40, 0x00, 0x10, 0x18, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x03, 0x09, 0xF0, 0xA6, 0x8C, 0x41, 0xF0, 0x00, 0x82, 0x04, 0x00, 0x88, 0x80,
|
||||
0x6C, 0x00, 0x80, 0xA0, 0xC0, 0x04, 0x12, 0x8A, 0x32, 0x58, 0x67, 0x33, 0x00, 0x38, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x40, 0x14, 0x80, 0x02, 0xE8, 0xB8, 0x01, 0x0F, 0x12, 0x08, 0x09, 0xCE,
|
||||
0x0C, 0xE0, 0x93, 0x08, 0x80, 0xC0, 0x00, 0x8A, 0x0C, 0xE1, 0x6C, 0x48, 0x80, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x18, 0x40, 0x0C, 0x0A, 0x63, 0x70, 0x68, 0xF0, 0x00, 0x02, 0x14, 0x21, 0x58, 0xC0,
|
||||
0x02, 0xD0, 0x00, 0x10, 0x43, 0x1B, 0x00, 0x95, 0x21, 0x94, 0xE0, 0x23, 0x93, 0x60, 0x00, 0x00,
|
||||
0x00, 0x08, 0x05, 0x03, 0x90, 0x18, 0x00, 0x2B, 0xC0, 0xC0, 0x00, 0x82, 0x31, 0x10, 0xC1, 0x69,
|
||||
0x80, 0xC0, 0x40, 0x00, 0x40, 0x06, 0x04, 0x0E, 0x4C, 0xA3, 0xD1, 0x08, 0x4C, 0x66, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x32, 0x06, 0x04, 0x02, 0x02, 0x00, 0x00, 0x4B, 0x90, 0x80, 0x11, 0x65,
|
||||
0x61, 0x00, 0x00, 0x18, 0x01, 0x80, 0x08, 0x00, 0x26, 0x0A, 0x86, 0x89, 0xE8, 0x20, 0x00, 0x00,
|
||||
0x00, 0x00, 0x02, 0xC0, 0x31, 0x99, 0xCF, 0x31, 0x20, 0x83, 0x00, 0x24, 0x66, 0x00, 0x20, 0x1F,
|
||||
0x00, 0x86, 0x0E, 0x83, 0x60, 0x00, 0x00, 0x00, 0x0D, 0x9B, 0x62, 0x90, 0x3D, 0x83, 0xB0, 0x00,
|
||||
0x00, 0x06, 0x78, 0x18, 0x62, 0x16, 0xAC, 0x08, 0x70, 0x7C, 0x00, 0x09, 0x4C, 0x5A, 0x01, 0x00,
|
||||
0xFC, 0xFC, 0x01, 0xC1, 0x78, 0x00, 0x08, 0x96, 0x41, 0x2C, 0x31, 0x08, 0x60, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x39, 0xC1, 0x68, 0x24, 0x44, 0x0C, 0x4C, 0x02, 0x0B, 0x81, 0x5C, 0x58, 0x9E,
|
||||
0x39, 0xFC, 0xE0, 0x88, 0x80, 0x00, 0x00, 0x0A, 0x84, 0x82, 0x82, 0x10, 0x20, 0x40, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x04, 0x9A, 0xD8, 0x2C, 0x11, 0xE1, 0xF8, 0x00, 0x24, 0x06, 0x00, 0x61, 0x80,
|
||||
0xD8, 0x00, 0x80, 0x07, 0x40, 0x00, 0x0B, 0x12, 0x19, 0x11, 0x4D, 0x26, 0x00, 0x70, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x40, 0x18, 0x50, 0x8C, 0xC1, 0x18, 0x00, 0x07, 0x01, 0xA5, 0x40, 0xD8,
|
||||
0x20, 0xC0, 0x09, 0x03, 0xE0, 0x00, 0x04, 0xC4, 0x84, 0xC3, 0x4C, 0x90, 0x4C, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x20, 0x60, 0x04, 0x28, 0x03, 0x50, 0xF4, 0xF0, 0x00, 0x24, 0x0D, 0x0C, 0xB2, 0xC0,
|
||||
0x09, 0xE0, 0xC0, 0x10, 0x30, 0xF3, 0x12, 0x33, 0x22, 0xAC, 0xA0, 0x4E, 0x0C, 0x80, 0x00, 0x00,
|
||||
0x00, 0x05, 0x2F, 0xA4, 0xE8, 0x10, 0x84, 0x88, 0x00, 0xC0, 0x00, 0x24, 0x12, 0xB8, 0x46, 0x22,
|
||||
0x80, 0x80, 0x00, 0x00, 0x20, 0x00, 0x01, 0x73, 0x42, 0x45, 0xA0, 0x30, 0x91, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x18, 0x04, 0x08, 0xCD, 0x03, 0x80, 0x00, 0x25, 0xB0, 0x40, 0x88, 0x20,
|
||||
0xC9, 0x00, 0x00, 0x04, 0x01, 0x80, 0xC0, 0x00, 0x09, 0x05, 0xCC, 0xC3, 0x01, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0xC0, 0x73, 0x99, 0x8A, 0x74, 0x00, 0x83, 0x02, 0x09, 0x20, 0x00, 0x82, 0x8D,
|
||||
0x00, 0x0C, 0x20, 0x05, 0xC0, 0x18, 0x00, 0x21, 0x05, 0x2A, 0xC4, 0x30, 0xF1, 0x03, 0x70, 0x00,
|
||||
0x00, 0x08, 0x60, 0x20, 0x30, 0x32, 0x8A, 0x50, 0xE4, 0x20, 0x00, 0x17, 0x01, 0x94, 0xC8, 0x03,
|
||||
0xE1, 0xF0, 0x43, 0x07, 0xC0, 0x00, 0x48, 0x12, 0x51, 0x05, 0x63, 0x10, 0xF0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x72, 0x62, 0x70, 0xB0, 0x44, 0x07, 0x24, 0x00, 0x25, 0x0A, 0x97, 0x24, 0xB0,
|
||||
0x3F, 0xF0, 0xC0, 0xA8, 0x00, 0x00, 0xC2, 0x24, 0x4C, 0x95, 0x20, 0x00, 0x60, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x0A, 0x04, 0xE6, 0xE8, 0x68, 0x32, 0x9E, 0xE0, 0x00, 0x09, 0x10, 0x80, 0xC4, 0x01,
|
||||
0x1C, 0x00, 0x80, 0x09, 0x20, 0x00, 0xC9, 0x2B, 0x2C, 0x25, 0x48, 0x64, 0x00, 0xF8, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x20, 0x20, 0x0C, 0x41, 0x14, 0x70, 0xC8, 0x00, 0x21, 0x4A, 0xD9, 0x88, 0x80,
|
||||
0x00, 0xF0, 0x01, 0x80, 0x18, 0x70, 0x10, 0x48, 0x89, 0xA7, 0x98, 0x30, 0x5C, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x2C, 0x84, 0x0C, 0x42, 0xF0, 0xA0, 0xF0, 0x00, 0x09, 0x50, 0x45, 0x65, 0xC0,
|
||||
0x47, 0xE0, 0x20, 0x04, 0x0C, 0x07, 0x01, 0xE2, 0x91, 0xD8, 0x85, 0x4D, 0x62, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x98, 0x90, 0xC4, 0x10, 0x0A, 0x53, 0x00, 0xC0, 0x03, 0x09, 0xC4, 0x44, 0x0E, 0x80,
|
||||
0x00, 0x80, 0x00, 0x00, 0x08, 0x01, 0x00, 0xCD, 0x85, 0x23, 0xC1, 0x31, 0x26, 0x30, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x08, 0x0C, 0x0E, 0x04, 0x38, 0xC3, 0x00, 0x01, 0x0A, 0xA0, 0x80, 0x08, 0x83,
|
||||
0xF1, 0x00, 0x80, 0x04, 0x03, 0x03, 0x98, 0x00, 0x02, 0xA3, 0x44, 0x4C, 0x16, 0x30, 0x00, 0x00,
|
||||
0x00, 0x00, 0x07, 0x80, 0xBB, 0x89, 0x94, 0x6C, 0x09, 0x0B, 0x00, 0x04, 0x0A, 0x00, 0x50, 0x91,
|
||||
0x01, 0x10, 0x01, 0x0F, 0x80, 0x30, 0x08, 0x31, 0x09, 0x29, 0x45, 0x61, 0x82, 0x04, 0x60, 0x00,
|
||||
0x00, 0x10, 0xC0, 0x00, 0x58, 0x22, 0x56, 0xA2, 0xC9, 0x1C, 0x00, 0x81, 0x04, 0x07, 0x92, 0x3F,
|
||||
0x9B, 0xC0, 0x08, 0x1E, 0x00, 0x00, 0x00, 0x82, 0x07, 0x97, 0x66, 0x41, 0xE0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xC4, 0x22, 0x38, 0x10, 0xC9, 0x12, 0x70, 0x00, 0x0B, 0x00, 0xBE, 0x40, 0x80,
|
||||
0x6F, 0xE0, 0xC0, 0x74, 0x00, 0x40, 0x91, 0x24, 0xC6, 0x88, 0x44, 0x20, 0xC4, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x21, 0x08, 0xC5, 0x6C, 0x44, 0x28, 0x33, 0xD8, 0x00, 0x04, 0x4A, 0x80, 0x50, 0x80,
|
||||
0x38, 0x01, 0x80, 0x80, 0xE0, 0x00, 0x21, 0x06, 0x9D, 0x69, 0x54, 0x48, 0x05, 0xA0, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02, 0x30, 0x18, 0x00, 0x80, 0x1E, 0xF8, 0x00, 0x0A, 0x00, 0xA8, 0x10, 0xC0,
|
||||
0x04, 0xE0, 0x04, 0x80, 0xC4, 0x20, 0x30, 0x45, 0x15, 0x86, 0xD1, 0x33, 0xBC, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x05, 0x0C, 0x2C, 0x40, 0xA2, 0xC8, 0xF0, 0x00, 0x04, 0x05, 0x02, 0x52, 0x80,
|
||||
0x1E, 0xC0, 0x00, 0x04, 0x03, 0x0F, 0x00, 0xC2, 0xDC, 0xA8, 0x4A, 0xCA, 0xCC, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x03, 0x49, 0x70, 0x10, 0x04, 0x8F, 0x04, 0xC0, 0x07, 0x04, 0x48, 0x82, 0x24, 0x1B,
|
||||
0x40, 0x80, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0xCA, 0xF5, 0xA1, 0x78, 0x1C, 0xE0, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0x06, 0x1A, 0x00, 0x23, 0x03, 0x06, 0x03, 0xE0, 0x00, 0x04, 0x4D,
|
||||
0xF0, 0x00, 0x00, 0x08, 0x02, 0x01, 0xFC, 0x00, 0x00, 0x45, 0x9A, 0xA3, 0x9B, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x2F, 0x01, 0xB3, 0x89, 0x90, 0xE0, 0x12, 0x13, 0x08, 0x41, 0x04, 0x80, 0xA0, 0x03,
|
||||
0x00, 0x00, 0x03, 0x5E, 0x08, 0x40, 0x00, 0x08, 0x8B, 0x49, 0x2B, 0x40, 0x04, 0x00, 0x80, 0x00,
|
||||
0x00, 0x01, 0x00, 0x81, 0x98, 0x47, 0x2B, 0xC6, 0x8B, 0x78, 0x22, 0x0A, 0x30, 0x8D, 0x08, 0x7F,
|
||||
0xE6, 0x00, 0x00, 0x07, 0x41, 0x00, 0x22, 0x12, 0x13, 0x0B, 0xCC, 0xC3, 0x84, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x80, 0x22, 0xB9, 0x32, 0x80, 0x29, 0xB8, 0x00, 0x1F, 0x05, 0x45, 0x30, 0x80,
|
||||
0x87, 0x80, 0xC0, 0xFC, 0x00, 0x00, 0x69, 0xE5, 0x4B, 0x8A, 0x28, 0x44, 0x98, 0x00, 0x80, 0x00,
|
||||
0x00, 0x00, 0x00, 0xB4, 0x40, 0x24, 0x04, 0x20, 0xE1, 0x78, 0x00, 0x43, 0x04, 0xC0, 0xA4, 0x80,
|
||||
0x60, 0x01, 0x00, 0x60, 0xF8, 0x00, 0x51, 0x8D, 0x54, 0x44, 0xC8, 0x98, 0x03, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02, 0x00, 0x30, 0x21, 0x81, 0x31, 0x70, 0x00, 0x01, 0x35, 0x41, 0x08, 0xC0,
|
||||
0x18, 0xE0, 0x00, 0xC4, 0xF0, 0x00, 0x61, 0x2A, 0x03, 0x2E, 0x93, 0x63, 0x60, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x80, 0x08, 0x02, 0x1D, 0xA0, 0xD0, 0x00, 0x41, 0x20, 0x91, 0xEC, 0xE0,
|
||||
0x17, 0xC0, 0x80, 0x02, 0x00, 0xFD, 0x00, 0x51, 0x75, 0x59, 0x1C, 0xB0, 0x30, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x08, 0xB6, 0x5C, 0x00, 0x03, 0x1F, 0x08, 0xC0, 0x02, 0x43, 0x11, 0x80, 0x01, 0x8D,
|
||||
0xC8, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xB0, 0x33, 0xC3, 0x72, 0x70, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x03, 0x00, 0x0C, 0x30, 0x80, 0x03, 0x06, 0x0E, 0x46, 0xA0, 0x80, 0x04, 0x84,
|
||||
0xA0, 0x01, 0x00, 0x00, 0x00, 0x03, 0xFE, 0x01, 0x00, 0xA3, 0x71, 0x00, 0x7C, 0xE0, 0x00, 0x00,
|
||||
0x00, 0x00, 0x7E, 0x83, 0x59, 0x89, 0x91, 0x41, 0x26, 0x63, 0x00, 0x0A, 0x12, 0x80, 0x15, 0x0F,
|
||||
0x31, 0x00, 0x06, 0x3C, 0x30, 0x00, 0x00, 0x85, 0x52, 0x89, 0x57, 0x82, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x2C, 0x0F, 0x8B, 0x8D, 0xD3, 0xF0, 0x00, 0x01, 0x29, 0x58, 0x19, 0x79,
|
||||
0xC0, 0x00, 0x01, 0xE2, 0x00, 0x00, 0x10, 0x91, 0x05, 0x12, 0xD0, 0xC3, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x40, 0x8E, 0x9D, 0x64, 0x50, 0x7A, 0xE0, 0x00, 0x45, 0x0A, 0x82, 0x02, 0x82,
|
||||
0x0E, 0x00, 0xC0, 0xF2, 0x01, 0x00, 0x04, 0xB5, 0x22, 0x95, 0x58, 0xC1, 0x30, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x68, 0x30, 0x30, 0x00, 0x42, 0xB6, 0xE8, 0x00, 0x0B, 0x12, 0xC0, 0x16, 0x80,
|
||||
0xC0, 0x01, 0x00, 0x1C, 0xC4, 0x00, 0x0C, 0x92, 0x49, 0x4C, 0xD0, 0x34, 0x10, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x02, 0x02, 0x20, 0x20, 0x42, 0x89, 0x20, 0x60, 0x00, 0x44, 0x12, 0x94, 0x5A, 0xC0,
|
||||
0x30, 0xE0, 0x00, 0x40, 0xD8, 0x08, 0x82, 0xAA, 0x11, 0x26, 0xC3, 0xE7, 0x19, 0x02, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x20, 0x84, 0x08, 0x48, 0xF8, 0x50, 0xC0, 0x00, 0x0A, 0x09, 0x38, 0x95, 0xF0,
|
||||
0xAD, 0xE0, 0x40, 0x03, 0x04, 0x03, 0x00, 0xA2, 0xBB, 0x3D, 0x9D, 0xA1, 0x48, 0x80, 0x20, 0x00,
|
||||
0x00, 0x00, 0x02, 0x08, 0x96, 0x00, 0x0E, 0x1F, 0x30, 0x80, 0x04, 0x0B, 0x26, 0x80, 0x80, 0xA0,
|
||||
0xD0, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x14, 0xC4, 0x1E, 0xC1, 0xF1, 0xE1, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xE0, 0x0C, 0x06, 0xC2, 0x73, 0x00, 0x07, 0x15, 0x20, 0x00, 0x05, 0x93,
|
||||
0x80, 0x80, 0x80, 0x11, 0x00, 0x0F, 0xF4, 0x38, 0x00, 0x56, 0xA2, 0x5B, 0x3E, 0x38, 0x00, 0x00,
|
||||
0x00, 0x00, 0xFF, 0x07, 0xA9, 0x03, 0x29, 0x95, 0x24, 0x41, 0x00, 0x01, 0x04, 0xC0, 0x80, 0x1E,
|
||||
0xE3, 0x00, 0x31, 0x78, 0xE1, 0x00, 0x00, 0x62, 0x49, 0x89, 0x97, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x44, 0x00, 0x04, 0x15, 0x2D, 0x8D, 0x06, 0x76, 0x84, 0x04, 0x4B, 0x02, 0xB7, 0xB8, 0x72,
|
||||
0x00, 0x00, 0x07, 0xF9, 0x04, 0x00, 0x08, 0x91, 0x2B, 0x95, 0xA1, 0x86, 0x08, 0x00, 0x80, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0x92, 0x0F, 0x46, 0x86, 0x71, 0x78, 0x00, 0x13, 0x27, 0x01, 0xC0, 0x00,
|
||||
0x04, 0x00, 0xC0, 0xFE, 0x02, 0x00, 0x24, 0x96, 0x25, 0x89, 0x71, 0x88, 0x40, 0x02, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x08, 0x30, 0x10, 0x00, 0x0D, 0x40, 0x00, 0x05, 0x06, 0xC0, 0x80, 0x83,
|
||||
0x01, 0x01, 0x00, 0x07, 0x02, 0x00, 0x0A, 0x89, 0x04, 0x1E, 0x40, 0x6A, 0x0A, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x04, 0x08, 0x00, 0x42, 0x21, 0x83, 0x40, 0x90, 0x00, 0x13, 0x01, 0x48, 0x98, 0xC0,
|
||||
0x60, 0xC0, 0x00, 0x21, 0xE4, 0x18, 0x00, 0x7A, 0x02, 0x6C, 0xA7, 0xEF, 0x26, 0x10, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x22, 0x00, 0x08, 0x01, 0x6C, 0x80, 0xC0, 0x00, 0x01, 0x02, 0x44, 0xD2, 0xF6,
|
||||
0x7F, 0xC0, 0x00, 0x01, 0x01, 0x0D, 0x00, 0x16, 0x11, 0x94, 0x13, 0x81, 0x90, 0x00, 0x80, 0x00,
|
||||
0x00, 0x00, 0x01, 0x84, 0x0C, 0x02, 0x18, 0x0E, 0xE0, 0x80, 0x00, 0x05, 0x54, 0x40, 0x00, 0x02,
|
||||
0xB0, 0x00, 0x00, 0x00, 0x02, 0x2D, 0x00, 0x03, 0x68, 0x0B, 0x87, 0xE7, 0x1C, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0x10, 0x52, 0x08, 0x19, 0x38, 0x7B, 0x00, 0x09, 0x08, 0xA0, 0x00, 0x05, 0x05,
|
||||
0xC0, 0x01, 0x00, 0x50, 0x00, 0x07, 0xDB, 0xF0, 0x00, 0x0F, 0xE1, 0x0C, 0x61, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x60, 0x14, 0x08, 0xC1, 0x80, 0xC0, 0x00, 0x00, 0xA6, 0x20, 0x00, 0x04, 0x44,
|
||||
0xC0, 0x01, 0x00, 0x02, 0x60, 0xF0, 0x00, 0x3F, 0x10, 0x13, 0x97, 0x38, 0x0E, 0x00, 0x00, 0x00,
|
||||
0x00, 0x15, 0x40, 0x88, 0x40, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x05, 0x03,
|
||||
0x3F, 0x3F, 0x00, 0xC2, 0x04, 0x07, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x28, 0xC0, 0x00, 0x00,
|
||||
0x00, 0x22, 0xFB, 0x36, 0x21, 0x02, 0x40, 0x00, 0x00, 0x10, 0x00, 0x01, 0x06, 0x00, 0xFF, 0xFB,
|
||||
0x76, 0x81, 0x00, 0x02, 0x04, 0x03, 0x30, 0x80, 0x8F, 0x83, 0xF3, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x89, 0x25, 0x20, 0x04, 0x00, 0x00, 0x07, 0xC1, 0x40, 0x00,
|
||||
0x02, 0xF8, 0x00, 0x00, 0x08, 0x0A, 0xB1, 0x03, 0x08, 0xC2, 0x08, 0xF8, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x6C, 0x14, 0x40, 0x40, 0x0B, 0x80, 0x02, 0x08, 0x48, 0x08,
|
||||
0x00, 0xF8, 0x00, 0x00, 0x00, 0x03, 0x30, 0xC5, 0xFF, 0x00, 0x77, 0x96, 0x6C, 0x60, 0x00, 0x00,
|
||||
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x28, 0xEC, 0x3C, 0xFE, 0x77, 0x90, 0x04, 0x08, 0x67, 0x29,
|
||||
0x6D, 0x20, 0x00, 0x0F, 0x00, 0x04, 0xF9, 0x7B, 0x42, 0xBB, 0xB0, 0x71, 0xB8, 0xC5, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x40, 0x04, 0x20, 0x00, 0x51, 0x6F, 0xDC, 0x00,
|
||||
0x57, 0xFE, 0x9A, 0xDE, 0xD5, 0x01, 0xFD, 0xFE, 0xDB, 0xFF, 0xEE, 0x68, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x42, 0x48, 0x64, 0x00, 0x80,
|
||||
0x08, 0x09, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x10, 0x38, 0x32, 0x00, 0xC1, 0x40, 0xC0, 0x00, 0x00, 0x03, 0x20, 0x00, 0x05, 0x88,
|
||||
0x81, 0x00, 0x00, 0x03, 0x20, 0xE0, 0x00, 0xFE, 0x41, 0x05, 0x2E, 0x16, 0x80, 0x20, 0x40, 0x00,
|
||||
0x00, 0x01, 0x14, 0x6B, 0x04, 0x01, 0x00, 0xF0, 0x01, 0x00, 0x08, 0x48, 0x20, 0x00, 0x05, 0x01,
|
||||
0x2F, 0x3F, 0x00, 0x00, 0x02, 0x07, 0x06, 0x10, 0x00, 0xC0, 0x00, 0x05, 0x76, 0x28, 0x00, 0x00,
|
||||
0x00, 0x05, 0xFC, 0x6D, 0x11, 0x00, 0x20, 0x00, 0x00, 0x30, 0x62, 0x60, 0x65, 0x00, 0xDB, 0xAF,
|
||||
0x24, 0x21, 0x00, 0x20, 0x12, 0x01, 0x01, 0x03, 0x7C, 0x87, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x8C, 0x17, 0x00, 0x00, 0x40, 0x06, 0x00, 0x04, 0xA4, 0x00,
|
||||
0x80, 0xB8, 0x00, 0x04, 0x00, 0x16, 0x8C, 0x08, 0x20, 0xC0, 0x61, 0xFC, 0x08, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x33, 0x26, 0x01, 0x04, 0x00, 0x10, 0x42, 0x20, 0x01, 0x04, 0x80,
|
||||
0x04, 0xB0, 0x00, 0x00, 0x40, 0x05, 0x30, 0x2B, 0x9F, 0x00, 0x0C, 0x1D, 0xC0, 0xC0, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x06, 0x89, 0x25, 0x99, 0x3D, 0x00, 0xC6, 0x45, 0xB4, 0x84,
|
||||
0xF4, 0x24, 0x81, 0x0E, 0x10, 0x02, 0x5D, 0xB9, 0x88, 0x6C, 0xE0, 0xB4, 0x1C, 0x10, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x08, 0x04, 0x80, 0x32, 0x18, 0xA0, 0x99, 0xA6, 0x89,
|
||||
0xCF, 0x73, 0x09, 0x88, 0x51, 0x84, 0xAB, 0xB5, 0xED, 0x2F, 0xB9, 0x80, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x80, 0x24, 0x40, 0x08, 0x08,
|
||||
0x40, 0x00, 0x02, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x40, 0x84, 0x68, 0x10, 0x82, 0x30, 0x00, 0x02, 0x02, 0x31, 0x20, 0x00, 0x05, 0x84,
|
||||
0x11, 0x00, 0x00, 0x01, 0x04, 0xC0, 0x03, 0xF9, 0x8C, 0x0E, 0x90, 0x0D, 0x00, 0xF0, 0x00, 0x00,
|
||||
0x00, 0x00, 0x0A, 0x37, 0x00, 0x01, 0x18, 0xA0, 0x00, 0x03, 0x0E, 0x60, 0x20, 0x00, 0x05, 0x0C,
|
||||
0x4D, 0x3F, 0x00, 0x01, 0x13, 0x03, 0x30, 0xC0, 0x04, 0xA0, 0x10, 0x01, 0xBC, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x6E, 0xD3, 0x01, 0x02, 0x00, 0x00, 0x00, 0x18, 0xB3, 0x30, 0x7A, 0x00, 0xEA, 0xC6,
|
||||
0x89, 0x43, 0x00, 0x01, 0x02, 0x11, 0x00, 0xCC, 0xF9, 0x83, 0x88, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x05, 0x9F, 0x42, 0x00, 0x21, 0x19, 0x10, 0x0F, 0x8D, 0x20,
|
||||
0x20, 0xF0, 0x00, 0x00, 0x04, 0x07, 0xC3, 0xC0, 0x00, 0xC1, 0xCC, 0xF8, 0x28, 0x40, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x93, 0x80, 0x8E, 0xC2, 0x33, 0x58, 0x23, 0x10, 0x00, 0xA1, 0x02,
|
||||
0x41, 0x70, 0x00, 0x00, 0x00, 0x01, 0x10, 0x01, 0x3F, 0x20, 0x01, 0x0B, 0x60, 0x28, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x32, 0x09, 0x38, 0xFE, 0x23, 0x30, 0x66, 0x2A, 0x13,
|
||||
0xA6, 0x08, 0x01, 0x0A, 0x58, 0x02, 0x75, 0xED, 0x84, 0x70, 0x24, 0x2E, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0x80, 0x00, 0x04, 0x20, 0xC4, 0x11, 0x3E, 0x34, 0x80, 0xCB, 0x0C,
|
||||
0x42, 0xA9, 0x00, 0xC4, 0xE5, 0x8C, 0x8C, 0xE2, 0x7C, 0x6F, 0x73, 0x40, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x10, 0x04,
|
||||
0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x28, 0x01, 0xB0, 0x00, 0x82, 0x08, 0x00, 0x03, 0x06, 0x1A, 0xA0, 0x00, 0x05, 0x40,
|
||||
0xA1, 0x00, 0x00, 0x00, 0x82, 0x40, 0x0F, 0xC0, 0xF8, 0x05, 0x28, 0x0B, 0x86, 0x79, 0x00, 0x00,
|
||||
0x00, 0x2B, 0x00, 0x4C, 0x24, 0x01, 0x3C, 0x90, 0x00, 0x03, 0x85, 0x20, 0x00, 0x00, 0x01, 0x24,
|
||||
0x3B, 0x1F, 0x00, 0x01, 0x83, 0x03, 0x83, 0x80, 0xF0, 0xC0, 0x42, 0x12, 0xF8, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x44, 0xE0, 0x01, 0x01, 0x00, 0x02, 0x00, 0x0C, 0x51, 0x03, 0x18, 0x82, 0x60, 0x12,
|
||||
0x5A, 0x33, 0x00, 0x10, 0x40, 0x11, 0x00, 0x20, 0xE0, 0xC3, 0x22, 0x03, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0xCA, 0x24, 0x22, 0x0C, 0xB0, 0xEC, 0xD2, 0x40, 0x00,
|
||||
0x60, 0xF0, 0x00, 0x00, 0x00, 0x07, 0xC0, 0xE8, 0x81, 0xC0, 0x78, 0xF8, 0x38, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0xC8, 0x03, 0x22, 0x19, 0x9E, 0x20, 0x80, 0x42, 0x10, 0x44,
|
||||
0x28, 0x70, 0x00, 0x00, 0x00, 0x01, 0x18, 0x00, 0x2D, 0x40, 0x04, 0x10, 0xC1, 0x70, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x73, 0x66, 0xCC, 0x09, 0x2A, 0xC9, 0x49,
|
||||
0x2B, 0x21, 0x00, 0x40, 0x70, 0x03, 0x7E, 0xCA, 0x26, 0xEA, 0x10, 0x0B, 0x80, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x40, 0x84, 0x1D, 0x18, 0x40, 0x2D, 0x64,
|
||||
0x24, 0xCC, 0x44, 0x48, 0xA0, 0x89, 0x24, 0x93, 0x28, 0x45, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
|
||||
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x04, 0x5D, 0xBE, 0x80, 0x00, 0x80, 0x04, 0x00, 0x01, 0x04, 0x07, 0x20, 0x00, 0x04, 0x44,
|
||||
0xE0, 0x41, 0x00, 0x08, 0xC0, 0x80, 0x3C, 0x01, 0x40, 0x0E, 0x90, 0x05, 0x23, 0x8E, 0x80, 0x00,
|
||||
0x00, 0x06, 0x40, 0xD5, 0x24, 0x01, 0x3C, 0x80, 0x03, 0x15, 0x00, 0x00, 0x20, 0x00, 0x05, 0x00,
|
||||
0x48, 0x0E, 0x00, 0x00, 0x89, 0x43, 0x1E, 0x0F, 0x80, 0x82, 0x80, 0x42, 0xE6, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x80, 0x40, 0x81, 0x02, 0x00, 0x01, 0x08, 0x07, 0x00, 0x81, 0xC3, 0xA3, 0x00, 0x01,
|
||||
0x8E, 0x23, 0x00, 0x10, 0x21, 0x18, 0x03, 0x00, 0x83, 0xC1, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x05, 0x6F, 0x41, 0x71, 0x18, 0x07, 0xBC, 0x9F, 0x68, 0x00, 0x0C,
|
||||
0x02, 0xF0, 0x00, 0x00, 0x00, 0x07, 0x42, 0x7E, 0x04, 0xC0, 0xE2, 0xF0, 0x58, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x4C, 0x30, 0x10, 0x80, 0x0F, 0xC0, 0x04, 0x10, 0x59, 0x20,
|
||||
0x04, 0xF0, 0x00, 0x00, 0x00, 0x01, 0x08, 0x10, 0x0B, 0xC0, 0x00, 0x01, 0x82, 0xE4, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x18, 0x44, 0x61, 0x55, 0xC3, 0xB8, 0x14, 0x94, 0x54, 0x80,
|
||||
0x52, 0x55, 0x00, 0x88, 0xD8, 0x05, 0x6A, 0xD9, 0xB2, 0xA0, 0x41, 0x13, 0x20, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04, 0x0E, 0x84, 0x69, 0x08, 0xD2,
|
||||
0xB2, 0x44, 0x80, 0x00, 0x80, 0x00, 0x00, 0x21, 0x24, 0x20, 0xA2, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x83, 0x3B, 0xC6, 0x18, 0x00, 0x00, 0x00, 0x80, 0x00, 0x60, 0x32, 0x00, 0x00, 0x05, 0x40,
|
||||
0xC0, 0xE2, 0x00, 0x00, 0x48, 0xC0, 0x40, 0x07, 0x32, 0x0D, 0x40, 0x00, 0x4C, 0xF0, 0x00, 0x00,
|
||||
0x00, 0x01, 0x18, 0xA4, 0x2E, 0x01, 0x3E, 0x00, 0x46, 0x00, 0x0C, 0x06, 0x00, 0x00, 0x01, 0x03,
|
||||
0x15, 0x00, 0x00, 0x08, 0x09, 0x06, 0x3C, 0x3C, 0x00, 0x81, 0x08, 0x29, 0xCC, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x99, 0x09, 0x00, 0x45, 0x2D, 0x80, 0xD4, 0x18, 0x93, 0x75, 0x00, 0x14,
|
||||
0x53, 0x43, 0x00, 0x08, 0x30, 0x28, 0x0F, 0x01, 0x1C, 0x83, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x25, 0x18, 0x7B, 0x80, 0x03, 0x1F, 0x0A, 0x00, 0x82, 0x1A,
|
||||
0x39, 0xE0, 0x00, 0x00, 0x00, 0x0B, 0x61, 0x3F, 0x00, 0xC0, 0x88, 0xF0, 0x54, 0x90, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x09, 0x0E, 0xA4, 0x11, 0x08, 0x00, 0xC0, 0x01, 0x02, 0x08, 0xAA, 0x32,
|
||||
0x80, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x08, 0x82, 0x00, 0xE1, 0x00, 0x16, 0x00, 0xD8, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x0C, 0x78, 0x13, 0x4A, 0x74, 0x3A, 0xA1, 0xBA, 0x21,
|
||||
0x45, 0xB2, 0x04, 0xC5, 0x68, 0x00, 0xB4, 0x94, 0xD1, 0xD4, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x12, 0x08, 0xAA, 0x20, 0x9B,
|
||||
0x92, 0xA0, 0xA8, 0x20, 0x00, 0x90, 0x52, 0x00, 0x08, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0xFF, 0xF0, 0x10, 0x00, 0x00, 0x11, 0x80, 0x08, 0x72, 0x3B, 0x20, 0x00, 0x04, 0x80,
|
||||
0xE0, 0xE0, 0x00, 0x10, 0x00, 0xB4, 0x00, 0x0E, 0xFD, 0x16, 0x00, 0x00, 0x52, 0x10, 0x00, 0x00,
|
||||
0x00, 0x04, 0x0E, 0x0E, 0x6E, 0x01, 0x7F, 0x00, 0x00, 0x00, 0x1F, 0x0F, 0x00, 0x00, 0x10, 0x09,
|
||||
0x23, 0x40, 0x00, 0x00, 0x4C, 0x40, 0x81, 0xF7, 0x18, 0xC5, 0x60, 0x92, 0x80, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xFD, 0x01, 0x00, 0x63, 0x7F, 0xD2, 0xFD, 0x1E, 0xBC, 0x3E, 0xA0, 0x42,
|
||||
0xB5, 0x17, 0x00, 0x08, 0x10, 0x0C, 0xE1, 0x0F, 0x61, 0x81, 0x00, 0x03, 0x04, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0x8C, 0x0D, 0x34, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
0x74, 0xE0, 0x00, 0x00, 0x80, 0x03, 0x64, 0x5D, 0xA1, 0x40, 0x40, 0xC0, 0xF5, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x40, 0x80, 0x18, 0xF8, 0x02, 0x8D, 0x8D, 0x20, 0x26, 0x50, 0xCC, 0x66, 0x11,
|
||||
0xA2, 0x79, 0x00, 0x80, 0x00, 0x00, 0x04, 0x21, 0x00, 0xC2, 0x00, 0x80, 0xD5, 0x70, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0xBC, 0x02, 0x05, 0xFA, 0x4D, 0x6B, 0x10, 0x04,
|
||||
0x06, 0xEB, 0x00, 0xD7, 0x2C, 0x02, 0x11, 0x08, 0xEB, 0xC0, 0x80, 0x09, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x02, 0x00, 0x02, 0x02, 0x32, 0xAF,
|
||||
0x48, 0x42, 0x0D, 0x00, 0x00, 0x02, 0x00, 0x10, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x2B, 0xEE, 0xF8, 0xE0, 0x00, 0x00, 0x0B, 0x00, 0x30, 0x31, 0x19, 0x00, 0x00, 0x05, 0x40,
|
||||
0xC0, 0xC0, 0x00, 0x20, 0x24, 0xFC, 0x00, 0x20, 0xFA, 0x29, 0x00, 0x03, 0xAF, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0x29, 0x5E, 0x01, 0x7F, 0x00, 0x10, 0x60, 0x2E, 0x07, 0x08, 0x00, 0x09, 0x00,
|
||||
0x01, 0x81, 0x00, 0x04, 0x04, 0x01, 0x0F, 0x9F, 0xE0, 0x81, 0xC2, 0x3F, 0x02, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xFF, 0x05, 0x81, 0x37, 0xBE, 0xF7, 0x7B, 0xCB, 0x11, 0xDF, 0x60, 0x26,
|
||||
0x08, 0x27, 0x00, 0x00, 0x08, 0x08, 0x3F, 0x79, 0x0F, 0xC1, 0x00, 0x07, 0x14, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x3E, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04,
|
||||
0x00, 0xE8, 0x00, 0x00, 0x80, 0x03, 0x21, 0x8B, 0xFB, 0x00, 0x00, 0x80, 0xD9, 0x20, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xA0, 0x00, 0x20, 0xAD, 0x40, 0xE6, 0xD8, 0x0D, 0x03, 0x32, 0x64, 0xD5, 0xA5,
|
||||
0x34, 0xEB, 0x00, 0xA0, 0x00, 0x00, 0x04, 0x09, 0x04, 0xFF, 0x00, 0x23, 0x82, 0x18, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x7F, 0x1C, 0xBF, 0x04, 0x00,
|
||||
0x82, 0xFD, 0x90, 0xFF, 0x04, 0x04, 0x00, 0x00, 0xFE, 0x90, 0x00, 0x4F, 0x80, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x20, 0x10, 0xFD,
|
||||
0x91, 0x10, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x41, 0x00, 0x00, 0x00, 0x41,
|
||||
0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x41, 0x41, 0x41, 0x48, 0x48, 0x41, 0x48, 0x48,
|
||||
0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x41, 0x41, 0x41, 0x48, 0x48, 0x4D, 0x4D, 0x41, 0x41, 0x4D,
|
||||
0x48, 0x48, 0x48, 0x48, 0x41, 0x41, 0x41, 0x41, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x41, 0x41, 0x41, 0x41, 0x48, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D,
|
||||
0x4D, 0x48, 0x4D, 0x48, 0x48, 0x48, 0x48, 0x41, 0x01, 0x01, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x41, 0x48, 0x41, 0x41, 0x48, 0x48, 0x41, 0x48, 0x4D, 0x4D, 0x4D, 0x4D,
|
||||
0x4D, 0x41, 0x4D, 0x48, 0x4D, 0x48, 0x48, 0x48, 0x01, 0x41, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0x41, 0x41, 0x41, 0x41, 0x41, 0x48, 0x41, 0x41, 0x41, 0x4D, 0x4D, 0x4D, 0x5D,
|
||||
0x4B, 0x4B, 0x4B, 0x4B, 0x41, 0x41, 0x48, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0x41, 0x41, 0x48, 0x41, 0x41, 0x41, 0x41, 0x01, 0x41, 0x41, 0x45, 0x45, 0x4D,
|
||||
0x4B, 0x4B, 0x41, 0x4B, 0x4B, 0x4B, 0x48, 0x48, 0x48, 0x41, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x41, 0x41, 0x41, 0x41, 0x41, 0x48, 0x48, 0x01, 0x41, 0x4D, 0x68, 0x69, 0x59,
|
||||
0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x41, 0x48, 0x48, 0x48, 0x41, 0x48, 0x41, 0x01, 0x01, 0x01, 0x00,
|
||||
0x00, 0x00, 0x01, 0x01, 0x41, 0x41, 0x41, 0x41, 0x48, 0x48, 0x01, 0x41, 0x4D, 0x43, 0x69, 0x59,
|
||||
0x4B, 0x59, 0x59, 0x4B, 0x4B, 0x4B, 0x4B, 0x48, 0x48, 0x48, 0x48, 0x41, 0x01, 0x01, 0x01, 0x00,
|
||||
0x00, 0x01, 0x01, 0x01, 0x41, 0x41, 0x41, 0x41, 0x48, 0x41, 0x01, 0x41, 0x4D, 0x4D, 0x69, 0x59,
|
||||
0x59, 0x59, 0x59, 0x59, 0x4B, 0x4B, 0x48, 0x48, 0x48, 0x48, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00,
|
||||
0x00, 0x00, 0x41, 0x41, 0x41, 0x48, 0x41, 0x41, 0x41, 0x43, 0x01, 0x41, 0x4D, 0x45, 0x69, 0x5D,
|
||||
0x5D, 0x5D, 0x43, 0x48, 0x4B, 0x4B, 0x48, 0x48, 0x48, 0x48, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00,
|
||||
0x00, 0x00, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x58, 0x43, 0x00, 0x41, 0x4D, 0x43, 0x69, 0x5D,
|
||||
0x5D, 0x58, 0x43, 0x41, 0x48, 0x4B, 0x48, 0x48, 0x48, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00,
|
||||
0x00, 0x00, 0x41, 0x43, 0x41, 0x41, 0x43, 0x43, 0x58, 0x43, 0x01, 0x41, 0x4D, 0x69, 0x69, 0x5D,
|
||||
0x5D, 0x43, 0x41, 0x41, 0x48, 0x48, 0x4B, 0x48, 0x48, 0x48, 0x41, 0x48, 0x41, 0x41, 0x00, 0x00,
|
||||
0x00, 0x00, 0x41, 0x43, 0x43, 0x43, 0x43, 0x58, 0x73, 0x46, 0x00, 0x41, 0x4D, 0x4D, 0x69, 0x5D,
|
||||
0x5E, 0x46, 0x41, 0x41, 0x01, 0x41, 0x48, 0x48, 0x48, 0x48, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00,
|
||||
0x00, 0x42, 0x42, 0x42, 0x42, 0x43, 0x43, 0x46, 0x70, 0x46, 0x41, 0x41, 0x4D, 0x45, 0x45, 0x4D,
|
||||
0x71, 0x46, 0x41, 0x00, 0x01, 0x41, 0x48, 0x48, 0x48, 0x48, 0x48, 0x41, 0x41, 0x41, 0x00, 0x00,
|
||||
0x00, 0x00, 0x42, 0x42, 0x42, 0x46, 0x46, 0x70, 0x70, 0x01, 0x05, 0x41, 0x05, 0x41, 0x01, 0x45,
|
||||
0x71, 0x46, 0x46, 0x01, 0x46, 0x43, 0x4B, 0x4B, 0x48, 0x48, 0x48, 0x41, 0x41, 0x41, 0x00, 0x00,
|
||||
0x00, 0x42, 0x42, 0x42, 0x42, 0x42, 0x70, 0x70, 0x46, 0x41, 0x05, 0x41, 0x45, 0x00, 0x01, 0x45,
|
||||
0x71, 0x46, 0x00, 0x01, 0x41, 0x58, 0x59, 0x59, 0x4B, 0x48, 0x48, 0x48, 0x41, 0x41, 0x41, 0x00,
|
||||
0x00, 0x42, 0x56, 0x50, 0x46, 0x46, 0x70, 0x46, 0x01, 0x05, 0x45, 0x45, 0x05, 0x00, 0x41, 0x45,
|
||||
0x4E, 0x70, 0x00, 0x01, 0x41, 0x43, 0x5E, 0x5E, 0x5E, 0x4B, 0x4B, 0x4B, 0x43, 0x43, 0x00, 0x00,
|
||||
0x00, 0x42, 0x42, 0x46, 0x70, 0x41, 0x41, 0x41, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x68, 0x68,
|
||||
0x69, 0x68, 0x00, 0x01, 0x41, 0x43, 0x5E, 0x73, 0x5E, 0x4B, 0x59, 0x58, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x41, 0x41, 0x4D, 0x69, 0x69, 0x68, 0x68, 0x68, 0x69, 0x68, 0x68,
|
||||
0x68, 0x45, 0x00, 0x01, 0x01, 0x45, 0x69, 0x71, 0x71, 0x4E, 0x5E, 0x43, 0x01, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x01, 0x41, 0x48, 0x4D, 0x68, 0x68, 0x68, 0x68, 0x69, 0x69, 0x69, 0x69,
|
||||
0x69, 0x45, 0x00, 0x45, 0x01, 0x45, 0x69, 0x69, 0x69, 0x4D, 0x4B, 0x48, 0x08, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0x00, 0x41, 0x41, 0x41, 0x45, 0x45, 0x45, 0x68, 0x69, 0x69, 0x4D, 0x4D,
|
||||
0x4D, 0x69, 0x41, 0x69, 0x45, 0x45, 0x4D, 0x4D, 0x69, 0x4D, 0x4D, 0x48, 0x01, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x41, 0x01, 0x41, 0x05, 0x45, 0x41, 0x41, 0x48,
|
||||
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x41, 0x41, 0x01, 0x01, 0x41,
|
||||
0x01, 0x01, 0x41, 0x00, 0x01, 0x41, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
Reference in New Issue
Block a user