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:
2026-09-14 16:00:05 +03:00
co-authored by Claude Sonnet 5
commit 6d835bc44c
377 changed files with 189430 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
# ESP-IDF build output (regenerate with `idf.py build`)
build/
.test-build/
managed_components/
sdkconfig.old
# Editor/IDE cruft
.vscode/
.idea/
*.user
# OS cruft
Thumbs.db
.DS_Store
+31
View File
@@ -0,0 +1,31 @@
diff --git a/components/console/linenoise/linenoise.c b/components/console/linenoise/linenoise.c
index 64fcf23a12..637e5cf8c0 100644
--- a/components/console/linenoise/linenoise.c
+++ b/components/console/linenoise/linenoise.c
@@ -930,14 +930,18 @@ static int linenoiseEdit(char *buf, size_t buflen, const char *prompt)
return l.len;
}
- if ( (getMillis() - t1) < LINENOISE_PASTE_KEY_DELAY && c != ENTER) {
- /* Pasting data, insert characters without formatting.
- * This can only be performed when the cursor is at the end of the
- * line. */
- if (linenoiseInsertPastedChar(&l,c)) {
- return -1;
- }
- continue;
+ unsigned char uc = (unsigned char)c;
+
+ if ((getMillis() - t1) < LINENOISE_PASTE_KEY_DELAY &&
+ uc >= 0x20 &&
+ uc != BACKSPACE &&
+ c != ENTER) {
+ /* Fast path only for printable pasted bytes.
+ * Control keys must go through normal key handling. */
+ if (linenoiseInsertPastedChar(&l, c)) {
+ return -1;
+ }
+ continue;
}
/* Only autocomplete when the callback is set. It returns < 0 when
+557
View File
@@ -0,0 +1,557 @@
# ESP32 SPI WiFi
## Description
The module is based on ESP32-S2 and provides high-speed WiFi access via SPI bus.
The key-features are:
- 4-wire 1-bit SPI interface, Slave Mode.
- Up to 14Mbit/s data transfer speed.
- DMA capable transfers.
## Programming
### Communication protocol
The SPI Slave communication protocol provides a few types of transactions: ***READ_REGS***, ***WRITE_REGS***, ***READ_DATA***, ***WRITE_DATA***.
#### READ_REGS
This type of transaction allows reading a number of bytes from the register memory. It can be used to read a register value or a status.
##### Scenario
| Step | Action |
| ---- | ---------------------------------------- |
| 1 | Assert SPI_CS. |
| 2 | Write SPI_DATA with **0x02**. |
| 3 | Write SPI_DATA with register number. |
| 4 | Write SPI_DATA with **two** dummy bytes. |
| 5 | Read one or more bytes from SPI_DATA. |
| 6 | De-assert SPI_CS. |
##### Read 1-byte register in C
```c
__sfr __at 0x57 SPI_DATA;
__sfr __at 0x77 SPI_CTRL;
#define SPI_ESP_CS_ON 0x0B
#define SPI_ESP_CS_OFF 0x03
u8 esp_rd_reg(u8 reg_num)
{
SPI_CTRL = SPI_ESP_CS_ON;
SPI_DATA = 0x02;
SPI_DATA = reg_num;
SPI_DATA; // dummy
SPI_DATA;
u8 rc = SPI_DATA;
SPI_CTRL = SPI_ESP_CS_OFF;
return rc;
}
```
#### WRITE_REGS
This type of transaction allows writing a number of bytes in the register memory. Not used except for command issuing.
Each WRITE_REGS transaction triggers the command execution. Thus, all arguments **must** be transmitted in the same transaction along with the command.
##### Scenario
| Step | Action |
| ---- | --------------------------------------- |
| 1 | Assert SPI_CS. |
| 2 | Write SPI_DATA with **0x01**. |
| 3 | Write SPI_DATA with register number. |
| 4 | Write SPI_DATA with **one** dummy byte. |
| 5 | Write SPI_DATA with one or more bytes. |
| 6 | De-assert SPI_CS. |
##### Write 1-byte register in C
The following code snippet is just an example not to use. Please refer to 'Write command' snippet for more practical case.
```c
__sfr __at 0x57 SPI_DATA;
__sfr __at 0x77 SPI_CTRL;
#define SPI_ESP_CS_ON 0x0B
#define SPI_ESP_CS_OFF 0x03
void esp_rd_reg(u8 reg_num, u8 reg_value)
{
SPI_CTRL = SPI_ESP_CS_ON;
SPI_DATA = 0x01;
SPI_DATA = reg_num;
SPI_DATA; // dummy
SPI_DATA = reg_value;
SPI_CTRL = SPI_ESP_CS_OFF;
}
```
#### WRITE COMMAND
This is a most used WRITE_REGS case, which triggers a command execution.
##### Write command in C
The following code snippet is just an example not to use. Please refer to 'Write command' snippet for more practical case.
```c
__sfr __at 0x57 SPI_DATA;
__sfr __at 0x77 SPI_CTRL;
#define SPI_ESP_CS_ON 0x0B
#define SPI_ESP_CS_OFF 0x03
void esp_cmd(u8 cmd)
{
SPI_CTRL = SPI_ESP_CS_ON;
SPI_DATA = 0x01;
SPI_DATA = 0x00; // ESP_REG_COMMAND
SPI_DATA; // dummy
SPI_DATA = cmd; // Command
SPI_DATA = 0x00; // Reset status
SPI_CTRL = SPI_ESP_CS_OFF;
}
```
#### READ_DATA
The transaction must only be launched when STATUS register contains DATA_S2M status.
The number of bytes to transfer must be read from xx_SIZE register before the transaction.
##### Scenario
| Step | Action |
| ---- | ------------------------------------------ |
| 1 | Assert SPI_CS. |
| 2 | Write SPI_DATA with **0x04**. |
| 4 | Write SPI_DATA with **three** dummy bytes. |
| 5 | Read one or more bytes from SPI_DATA. |
| 6 | De-assert SPI_CS. |
| 7 | Execute `esp_cmd(DATA_END);` |
##### Read a data array in C
```c
__sfr __at 0x57 SPI_DATA;
__sfr __at 0x77 SPI_CTRL;
#define SPI_ESP_CS_ON 0x0B
#define SPI_ESP_CS_OFF 0x03
void esp_recv(u8 *addr, u16 num)
{
SPI_CTRL = SPI_ESP_CS_ON;
SPI_DATA = 0x04;
SPI_DATA; // dummy read means write with 0xFF
SPI_DATA;
SPI_DATA;
while (num--) *addr++ = SPI_DATA;
SPI_CTRL = SPI_ESP_CS_OFF;
esp_cmd(ESP_CMD_DATA_END);
}
```
#### WRITE_DATA
The transaction must only be launched when STATUS register contains DATA_M2S status.
The number of bytes to transfer must be read or written from/to xx_SIZE register before the transaction.
**ATTENTION!**
When writing data, all transactions **must** be truncated to 4 bytes. If we need to write 1 byte only, we must write 4 bytes and so on.
*To do: add code example.*
### Registers
All registers are read/writeable from both SPI Master via SPI and Slave firmware. At a low level, they're a shared memory cells inside ESP32 Slave. The total number of registers is 64.
Common registers are COMMAND and STATUS. Registers with addresses 2-63 are used by the ESP32 firmware for different functions depending on a command.
#### Register summary
| Name | Register address, HEX | Size | Description |
| ---------- | ------------ | ----- | ------------------------------------------------------------ |
| COMMAND | 00 | 1 | Command. Set by Master. |
| STATUS | 01 | 1 | Status of device. Set by Slave while the command is being executed or after its execution. Must be zeroed by Master in the same transaction as the command issued, to ensure the correct synchronization. |
| PARAM/RESP | 02 | 0..61 | If written, **must** be sent in the same transaction as the command issued.<br />See parameter/response list for individual commands. |
#### STATUS register
| Name | Code, HEX | Description |
| -------- | --------- | ------------------------------------------------------------ |
| IDLE | 00 | No status. Must be set by Master along every command issued. |
| BUSY | 01 | Device is busy executing a command. |
| READY | 02 | Device has completed command execution and is ready for a new command. |
| ERROR | 03 | Error happened. Use GET_ERROR command to get the error code. |
| DATA_M2S | 04 | Device is ready to receive data from host. |
| DATA_S2M | 05 | Device is ready to send data to host. |
#### ERROR register
If a command has been completed with an error, the register 02 automatically indicates error code.
| Name | Register address, HEX | Size | Description |
| ----- | ------------ | ---- | ----------- |
| ERROR | 02 | 1 | Error. |
The following table contains error codes.
| Name | Code, HEX | Description |
| --------------- | --------- | -------------------------------------------- |
| NONE | 00 | No error. |
| WRONG_PARAMETER | 01 | Wrong parameter. |
| INVALID_COMMAND | 02 | Invalid command. |
| WRONG_STATE | 03 | Command cannot be executed in current state. |
| AP_NOT_CONN | 10 | Cannot connect AP. |
| RESET | FE | Is set when reset happened. |
### Commands
#### Command summary
| Name | Code, HEX | Description |
| ---------------- | --------- | ------------------------------------------------------------ |
| NOP | 00 | No operation. Issuing this command has no effect. |
| GET_ERROR | | Get extended error info. |
| GET_INFO | 02 | Get device info string. |
| GET_CHIP_INFO | 03 | Get chip info. |
| GET_CAPS | | Get device capabilities. |
| GET_HEAP | | Get heap size. |
| GET_NET_CAPS | | Get network capabilities. |
| GET_NETSTATE | 11 | Get network connection state. |
| WSCAN | 12 | Scan Wi-Fi APs. |
| SET_AP_NAME | 13 | Set Wi-Fi AP name. |
| SET_AP_PWD | 14 | Set Wi-Fi AP password. |
| GET_AP_NAME | 15 | Get Wi-Fi AP name. |
| AP_CONNECT | 16 | Connect to selected AP. |
| AP_DISCONNECT | 17 | Disconnect from AP. |
| SET_AUTOCONNECT | 18 | Set AP auto-connect. |
| GET_IP | 19 | Get own Wi-Fi IP, net mask, net gateway. |
| RESOLVE_DNS | | Resolve DNS name. |
| OPEN_UDP | | |
| CLOSE_UDP | | |
| STATUS_UDP | | |
| SEND_DATAGR | | |
| RECV_DATAGR | | |
| OPEN_TCP | | |
| CLOSE_TCP | | |
| STATUS_TCP | | |
| SEND_TCP | | |
| RECV_TCP | | |
| | | |
| SET_WSCAN_PARAMS | | Set parameters for Wi-Fi AP scan. |
| | | |
| EXT_STATUS_TCP | | ? |
| CONF_AUTO_IP | | ? |
| CONF_IP | | ? |
| | | |
| DATA_END | D0 | End DATA transaction. (A workaround for S3 CMD8 bug). |
| BREAK | E0 | Break an operation. |
| RESET | EE | Reset device. To be issued in the case of an unpredicted behavior. The reset takes <TBD> milliseconds to complete. The STATUS register after the reset must contain READY value. The EXT_STAT register must contain RESET value. |
| GET_RND | F0 | Generate random numbers array. |
| TEST2 | F1 | Generate tunnel DL for FT812. |
| TEST3 | F2 | Generate 3D model DL for FT812. |
#### Miscellaneous commands
##### GET_INFO
Get device information string. The string is **not** 0-terminated.
**In:** none.
**Out:**
| Name | Register address, HEX | Size | Description |
| --------- | ------------ | ---- | -------------------------------- |
| INFO_SIZE | 02 | 1 | Info string size. |
[DATA] - Info string.
##### GET_CHIP_INFO
Get ESP module hardware parameters.
**In:** none.
**Out:** See table below.
| Name | Register address, HEX | Size, bytes | Description |
| --------- | ------------ | ---- | -------------------------------- |
| CPU_TYPE | 02 | 1 | ESP chip type, hex:<br />02 - ESP32-S3 |
| CPU_CORES | 03 | 1 | Number of CPU cores. |
| CPU_FREQ | 04 | 2 | CPU frequency, MHz. |
| CPU_SI_REV | 06 | 1 | Silicon revision. |
| WIFI | 07 | 1 | |
| BLE | 08 | 1 | |
| FLASH_SIZE | 09 | 1 | Flash size, MB. |
| FLASH_TYPE | 0A | 1 | |
| FLASH_FREQ | 0B | 1 | |
| PSRAM_SIZE | 0C | 1 | PSRAM size, MB. |
| PSRAM_TYPE | 0D | 1 | |
| PSRAM_FREQ | 0E | 1 | |
##### GET_CAPS
Read device capabilities.
**In:** none.
**Out:**
| Name | Bit | Mask, Hex | Description |
| ---- | ---- | --------- | ----------- |
| | | | |
##### BREAK
Break execution of current command. If no command is being executed, this has no effect.
**In:** none.
**Out:** none.
#### Wi-Fi commands
Most Wi-Fi commands require time to complete. While in progress, the STATUS register indicates BUSY state. It changes to READY upon command completion if successful, or to ERROR, if not.
##### GET_NETSTATE
Get network state.
**In:** none.
**Out:** Network state. See the table below.
| Name | Register address, HEX | Size | Description |
| -------- | ------------ | ---- | -------------- |
| NETSTATE | 02 | 1 | Network state. |
**NETSTATE**
| Name | Code, HEX | Description |
| --------------- | --------- | ---------------------------------- |
| NETWORK_CLOSED | 00 | No network connection established. |
| NETWORK_OPENING | 01 | Network connection in progress. |
| NETWORK_OPEN | 02 | Network connection established. |
| NETWORK_CLOSING | 03 | Network disconnection in progress. |
| NETWORK_UNKNOWN | FF | Network state undefined. |
##### WSCAN
Scan for Wi-Fi APs. Scanned APs are sorted by RSSI, highest first.
Requires STATUS register polling to check completion.
**In:**
| Name | Register address, HEX | Size | Description |
| --------- | ------------ | ---- | -------------------------------- |
| AP_MAX | 02 | 1 | Max number of AP to scan. |
**Out:**
| Name | Register address, HEX | Size | Description |
| --------- | ------------ | ---- | -------------------------------- |
| AP_TABLE_SIZE | 02 | 2 | AP table size. |
[DATA] - scanned AP table. See the format below.
| Name | Size, bytes | Description |
| ------------------------------ | ------------- | ---------------------- |
| NUM_APS | 1 | Number of APs scanned. |
| AP_AUTH | 1 | AP authorization type. |
| AP_RSSI | 1 | AP RSSI, dB. |
| AP_CHAN | 1 | AP primary channel |
| AP_SSID_LEN | 1 | AP SSID length. |
| AP_SSID | <AP_SSID_LEN> | AP SSID. |
| ... repeat AP_xx for <NUM_APS> | | |
**APn_AUTH**
| Name | Value, HEX | Description |
| ------------------ | ---------- | ----------- |
| AP_AUTH_OPEN | 00 | Open |
| AUTH_WEP | 01 | |
| AUTH_WPA_PSK | 02 | |
| AUTH_WPA2_PSK | 03 | |
| AUTH_WPA_WPA2_PSK | 04 | |
| AUTH_EAP | 05 | |
| AUTH_WPA3_PSK | 06 | |
| AUTH_WPA2_WPA3_PSK | 07 | |
| AUTH_WAPI_PSK | 08 | |
| AUTH_OWE | 09 | |
| AUTH_WPA3_ENT_192 | 0A | |
| AUTH_MAX | 0B | |
##### SET_AP_NAME
Set AP name. 32 bytes max.
**In:**
| Name | Register address, HEX | Size | Description |
| --------- | ------------ | ---- | -------------------------------- |
| AP_NAME_LEN | 02 | 1 | AP name length. |
| AP_NAME | 03 | <AP_NAME_LEN> | AP name. |
**Out:** none.
##### SET_AP_PWD
Set AP password. 61 bytes max.
**In:**
| Name | Register address, HEX | Size | Description |
| --------- | ------------ | ---- | -------------------------------- |
| AP_PWD_LEN | 02 | 1 | AP password length. |
| AP_PWD | 03 | <AP_PWD_LEN> | AP password. |
**Out:** none.
##### GET_AP_NAME
Set AP name. 32 bytes max.
**In:** none.
**Out:**
| Name | Register address, HEX | Size | Description |
| --------- | ------------ | ---- | -------------------------------- |
| AP_NAME_LEN | 02 | 1 | AP name length. |
| AP_NAME | 03 | <AP_NAME_LEN> | AP name. |
##### **AP_**CONNECT
Connect to the selected AP.
Requires STATUS register polling to check completion.
**In:** none.
**Out:** none.
##### AP_DISCONNECT
Disconnect AP.
Requires STATUS register polling to check completion.
**In:** none.
**Out:** none.
##### SET_AUTOCONNECT
Enable or disable auto-connect.
SET_AP_NAME and SET_AP_PWD must be set before this command.
**In:**
| Name | Register address, HEX | Size | Description |
| --------- | ------------ | ---- | -------------------------------- |
| AUTOCONNECT | 02 | 1 | Mode:<br />0 - disabled.<br />1 - enabled. |
**Out:** none.
##### GET_IP
Get network IP addresses.
**In:** none.
**Out:**
| Name | Register address, HEX | Size | Description |
| --------- | ------------ | ---- | -------------------------------- |
| IP | 02 | 4 | Last DNS resolved IP address. |
| OWN_IP | 06 | 4 | Own IP address. |
| MASK | 0A | 4 | Net mask. |
| GATE | 0E | 4 | Net gateway. |
##### RESOLVE_DNS
Resolve IP address from domain name, which length must not be longer than 255 symbols.
Requires STATUS register polling to check completion.
**In:**
| Name | Register address, HEX | Size | Description |
| --------- | ------------ | ---- | -------------------------------- |
| DNAME_LEN | 02 | 1 | Domain name length. |
| DNAME | 03..3F | 1..61 | Domain name. |
If DNAME_LEN<= 61, then it is sent using registers 03-3F. If it is longer than 61 symbols, send the symbols starting from 62 using DATA transaction (currently not supported).
**Out:**
Same as GET_IP.
+111
View File
@@ -0,0 +1,111 @@
# ESP32 firmware (ZX Evolution / TS-Conf ZiFi32 module)
Compiled firmware for the ESP32-S3-based WiFi/SPI module used by the ZX
Evolution / TS-Conf ("Pentevo") platform.
**Base source**: this firmware is based on
[tslabs/zx-evo](https://github.com/tslabs/zx-evo/tree/master/pentevo/esp32) —
full source for this fork lives in `pentevo/esp32/src` alongside this folder;
this `firmware/` directory only holds ready-to-flash binaries.
## Requirements
The ZX Evolution/TS-Conf board's own core firmware must be built with
**VDAC2** and **ESP32 (ZiFi32)** peripheral support enabled - without both,
the core won't recognize the ESP32 module on the SPI bus at all (no matter
how correct this ESP32-side firmware is), and won't drive the FT8xx video
chip either. Check this before assuming a connection problem is on the
ESP32 side.
The **RESET jumper on the IDE connector must be installed** as well - see
`../hardware/connection_diagram.webp`.
## What's changed vs. the upstream source
Most of the work in this fork is a **VFS bridge that exposes the ZX
Evolution's real SD card as a network share over SMB2/3**, so any Windows/
macOS/Linux SMB client can browse, download, upload to, and delete files on
the real Wild Commander filesystem without pulling the card. This is still
**actively being worked on** — see "Status" below.
Changes so far, roughly in the order they were made:
- **Real filesystem wired up.** `vfs_bridge.cpp`'s SMB2 handlers
(CREATE/READ/WRITE/CLOSE/QUERY_DIRECTORY/QUERY_INFO/etc.) now talk to the
real filesystem via a small request/response bridge (`vfs_request.cpp`)
over the existing SPI link to the Z80 side, instead of the original
hardcoded demo filesystem.
- **Upload (WRITE) and delete support** for files and empty directories,
including proper `FILE_DELETE_ON_CLOSE` handling and a
`SMB2_STATUS_DIRECTORY_NOT_EMPTY` reply when a non-empty directory delete
is rejected.
- **Directory listing fixes**: `SMB2_RETURN_SINGLE_ENTRY` pagination support
(needed by `cmd.exe`'s own SMB2 client, not just Explorer), the missing
non-"ID" directory info classes, synthesized `.`/`..` entries, and a
`NextEntryOffset` encoding bug in the vendored `libsmb2` that silently
corrupted any listing past the second entry.
- **`srvsvc` share-enumeration fix**: the RPC pipe used to always report the
Phase-1 placeholder share name `"test"` regardless of what a client had
actually connected to — fixed to report the real share name.
- **Added `SMB2_FILE_FS_VOLUME_INFORMATION`** support (a bare `dir`/`net use`
needs it; Explorer doesn't ask for it, which is why this was missed at
first).
- **Stability fixes**: a NULL-pointer crash in the named-pipe RPC transceive
path, and a 0-length SPI DMA transaction that could wedge the whole SPI
channel to the Z80 side.
- **`libsmb2` (vendored, `src/third_party/libsmb2`) patches**:
- `SMB2_FSCTL_DFS_GET_REFERRALS` now answers `STATUS_OBJECT_PATH_NOT_FOUND`
instead of `STATUS_NOT_IMPLEMENTED` — the wrong status code was sending
Windows Explorer into a retry storm before it would even attempt to
connect to the share.
- `SET_INFO` parsing is now permissive for info classes this server
doesn't specifically implement (passes the raw buffer through instead of
failing to parse) — a hard parse failure here used to tear down the
*entire* TCP connection instead of just failing that one request, which
showed up as Explorer aborting mid file-copy with a generic error.
- A `close_cmd` return value of `-2` now maps to
`SMB2_STATUS_DIRECTORY_NOT_EMPTY` in the CLOSE reply instead of a generic
`STATUS_NOT_IMPLEMENTED`.
- **Auth simplified** to plain guest/anonymous sessions (`authorize_handler`
always succeeds) — an earlier attempt at forcing NTLMv2 + SMB2 signing
turned out to be unrelated to a since-fixed listing bug, so it was backed
out rather than kept as unnecessary complexity.
- Removed high-frequency per-request debug logging that was originally added
to chase the above bugs, replaced with a single per-connection summary
line.
## Status
The SMB2/3 server (read, browse, upload, delete) works against real Windows/
Samba clients. **This is still in-progress work** — directory/file creation
(`MKDIR`) isn't implemented yet, overwriting an existing file with shorter
content doesn't truncate the old tail, and there's no live-update
(`change_notify`) support, so Explorer can show a stale listing for a moment
after a change until it's refreshed. See the source tree's own code comments
for the current state of any specific handler.
## Flashing
Target chip: **ESP32-S3**. Using `esptool.py` directly:
```bash
esptool.py --chip esp32s3 -p COM5 -b 460800 write_flash \
--flash-mode dio --flash-freq 80m --flash-size detect \
0x0 bootloader.bin \
0x8000 partition-table.bin \
0x10000 esp-spi.bin
```
(replace `COM5` with the actual port; `flasher_args.json` in this folder has
the same offsets in `idf.py`'s own JSON format, for reference or scripting.)
If you're building from source instead: ESP-IDF **v6.1** (not `v6.1-rc1`) —
`idf.py build flash`.
## Hardware
See `../hardware/` for:
- `connection_diagram.webp` — how this module wires up to a ZX Evolution
board.
- `pcb_photo.jpg` — a photo of the adapter board.
- `gerber/` — Gerber files for the adapter PCB.
Binary file not shown.
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
{
"write_flash_args" : [ "--flash-mode", "dio",
"--flash-size", "detect",
"--flash-freq", "80m" ],
"flash_settings" : {
"flash_mode": "dio",
"flash_size": "detect",
"flash_freq": "80m"
},
"flash_files" : {
"0x0" : "bootloader/bootloader.bin",
"0x8000" : "partition_table/partition-table.bin",
"0x10000" : "esp-spi.bin"
},
"bootloader" : { "offset" : "0x0", "file" : "bootloader/bootloader.bin", "encrypted" : "false" },
"partition-table" : { "offset" : "0x8000", "file" : "partition_table/partition-table.bin", "encrypted" : "false" },
"app" : { "offset" : "0x10000", "file" : "esp-spi.bin", "encrypted" : "false" },
"extra_esptool_args" : {
"after" : "hard-reset",
"before" : "default-reset",
"stub" : true,
"chip" : "esp32s3"
}
}
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

+216
View File
@@ -0,0 +1,216 @@
;TYPE=PLATED
;Layer: PTH_Through
;EasyEDA Pro v3.2.148, 2026-08-18 14:31:00
;Gerber Generator version 0.3
M48
METRIC,LZ,0000.00000
;Hole size 1 = 0.91440 METRIC
T01C0.91440
;Hole size 2 = 0.96520 METRIC
T02C0.96520
;Hole size 3 = 1.00000 METRIC
T03C1.00000
%
G05
G90
T01
X27.33397Y125.72895
X27.33397Y123.18895
X27.33397Y120.64895
X27.33397Y118.10895
X27.33397Y115.56895
X27.33397Y113.02895
X27.33397Y110.48895
X27.33397Y107.94895
X27.33397Y105.40895
X27.33397Y102.86895
X27.33397Y100.32895
X27.33397Y97.78895
X27.33397Y95.24895
X27.33397Y92.70895
X27.33397Y90.16895
X27.33397Y87.62895
X27.33397Y85.08895
X27.33397Y82.54895
X27.33397Y80.00895
X27.33397Y77.46895
X27.33397Y74.92895
X27.33397Y72.38895
X1.93402Y72.38895
X1.93402Y74.92895
X1.93402Y77.46895
X1.93402Y80.00895
X1.93402Y82.54895
X1.93402Y85.08895
X1.93402Y87.62895
X1.93402Y90.16895
X1.93402Y92.70895
X1.93402Y95.24895
X1.93402Y97.78895
X1.93402Y100.32895
X1.93402Y102.86895
X1.93402Y105.40895
X1.93402Y107.94895
X1.93402Y110.48895
X1.93402Y113.02895
X1.93402Y115.56895
X1.93402Y118.10895
X1.93402Y120.64895
X1.93402Y123.18895
X1.93402Y125.72895
X-2.765Y74.929
X-2.765Y77.469
X-2.765Y80.009
X-2.765Y82.549
X-2.765Y85.089
X-2.765Y87.629
X-2.765Y90.169
X-2.765Y92.709
X-2.765Y95.249
X-23.085Y90.169
X-23.085Y87.629
X-23.085Y85.089
X-23.085Y82.549
X-23.085Y80.009
X-23.085Y77.469
X6.125Y5.46
X3.585Y5.46
X6.125Y8.0
X3.585Y8.0
X6.125Y10.54
X3.585Y10.54
X6.125Y13.08
X3.585Y13.08
X6.125Y15.62
X3.585Y15.62
X6.125Y18.16
X3.585Y18.16
X6.125Y20.7
X3.585Y20.7
X6.125Y23.24
X3.585Y23.24
X6.125Y25.78
X3.585Y25.78
X6.125Y28.32
X3.585Y28.32
X6.125Y30.86
X3.585Y30.86
X6.125Y33.4
X3.585Y33.4
X6.125Y35.94
X3.585Y35.94
X6.125Y38.48
X3.585Y38.48
X6.125Y41.02
X3.585Y41.02
X6.125Y43.56
X3.585Y43.56
X6.125Y46.1
X3.585Y46.1
X6.125Y48.64
X3.585Y48.64
X6.125Y51.18
X3.585Y51.18
X6.125Y53.72
X3.585Y53.72
X38.2941Y60.08016
X35.7541Y60.08016
X38.2941Y62.62016
X35.7541Y62.62016
X38.2941Y65.16016
X35.7541Y65.16016
X38.2941Y67.70016
X35.7541Y67.70016
X38.2941Y70.24016
X35.7541Y70.24016
X38.2941Y72.78016
X35.7541Y72.78016
X38.2941Y75.32016
X35.7541Y75.32016
X38.2941Y77.86016
X35.7541Y77.86016
X38.2941Y80.40016
X35.7541Y80.40016
X38.2941Y82.94016
X35.7541Y82.94016
X35.5Y96.19
X32.96Y96.19
X35.5Y98.73
X32.96Y98.73
X35.5Y101.27
X32.96Y101.27
X35.5Y103.80997
X32.96Y103.80997
X35.5Y106.34997
X32.96Y106.34997
X20.91542Y100.08008
X20.91542Y97.54008
X18.37542Y100.08008
X18.37542Y97.54008
X15.83542Y100.08008
X15.83542Y97.54008
X13.29542Y100.08008
X13.29542Y97.54008
X10.75542Y100.08008
X10.75542Y97.54008
X45.5Y96.19
X42.96Y96.19
X45.5Y98.73
X42.96Y98.73
X45.5Y101.27
X42.96Y101.27
X45.5Y103.80997
X42.96Y103.80997
X45.5Y106.34997
X42.96Y106.34997
X-23.085Y107.822
X-20.545Y107.822
X-18.005Y107.822
X-15.465Y107.822
T02
X11.967Y35.94
X11.967Y38.48
T03
X19.46Y53.72
X22.0Y53.72
X19.46Y51.18
X22.0Y51.18
X19.46Y48.64
X22.0Y48.64
X19.46Y46.1
X22.0Y46.1
X19.46Y43.56
X22.0Y43.56
X19.46Y41.02
X22.0Y41.02
X19.46Y38.48
X22.0Y38.48
X19.46Y35.94
X22.0Y35.94
X19.46Y33.4
X22.0Y33.4
X19.46Y30.86
X22.0Y30.86
X19.46Y28.32
X22.0Y28.32
X19.46Y25.78
X22.0Y25.78
X19.46Y23.24
X22.0Y23.24
X19.46Y20.7
X22.0Y20.7
X19.46Y18.16
X22.0Y18.16
X19.46Y15.62
X22.0Y15.62
X19.46Y13.08
X22.0Y13.08
X19.46Y10.54
X22.0Y10.54
X19.46Y8.0
X22.0Y8.0
X19.46Y5.46
X22.0Y5.46
X15.777Y42.10107
X15.777Y33.40096
M30
+996
View File
@@ -0,0 +1,996 @@
{
"lengthUnit": "mil",
"remark": [],
"components": {
"fields": ["COMPONENT_NO", "COMPONENT_NAME", "LAYER", "X_COORDINATE", "Y_COORDINATE", "ANGLE"],
"rows": [
[1, "C65", "T", 726.1417, 2454.9606, 180],
[2, "J1", "T", 471.1417, 1464.9606, 270],
[3, "R1", "B", 228.7417, 3852.4908, 180],
[4, "R2", "B", 255.7417, 3250.4908, 0],
[5, "U1", "T", 576.1417, 3949.9606, 0],
[6, "U3", "T", -508.8583, 3349.9606, 90],
[7, "VD1", "T", 621.1417, 1486.2606, 270],
[8, "X1", "T", 816.1417, 1164.9606, 90],
[9, "X2", "T", 191.1417, 1164.9606, 90],
[10, "X3", "T", 1457.6417, 2815.3606, 270],
[11, "X4", "T", 1347.6378, 3987.0078, 90],
[12, "X5", "T", 623.4417, 3890.1606, 0],
[13, "X6", "T", 1741.3386, 3987.0078, 90],
[14, "X7", "T", -758.8583, 4244.9606, 0],
[15, "PAD1", "T", 471.1417, 1414.9606, 0],
[16, "PAD1", "T", 471.1417, 1414.9606, 0],
[17, "PAD2", "T", 471.1417, 1514.9606, 0],
[18, "PAD2", "T", 471.1417, 1514.9606, 0],
[19, "PAD3", "B", 189.3717, 3852.4908, 0],
[20, "PAD4", "B", 268.1117, 3852.4908, 0],
[21, "PAD5", "B", 295.1117, 3250.4908, 0],
[22, "PAD6", "B", 216.3717, 3250.4908, 0],
[23, "PAD7", "T", 1076.1417, 4949.9606, 0],
[24, "PAD7", "T", 1076.1417, 4949.9606, 0],
[25, "PAD8", "T", 1076.1417, 2949.9606, 0],
[26, "PAD8", "T", 1076.1417, 2949.9606, 0],
[27, "PAD9", "T", 1076.1417, 2849.9606, 0],
[28, "PAD9", "T", 1076.1417, 2849.9606, 0],
[29, "PAD10", "T", 76.1417, 2849.9606, 0],
[30, "PAD10", "T", 76.1417, 2849.9606, 0],
[31, "PAD11", "T", 76.1417, 2949.9606, 0],
[32, "PAD11", "T", 76.1417, 2949.9606, 0],
[33, "PAD12", "T", 76.1417, 3149.9606, 0],
[34, "PAD12", "T", 76.1417, 3149.9606, 0],
[35, "PAD13", "T", 76.1417, 3249.9606, 0],
[36, "PAD13", "T", 76.1417, 3249.9606, 0],
[37, "PAD14", "T", 76.1417, 3349.9606, 0],
[38, "PAD14", "T", 76.1417, 3349.9606, 0],
[39, "PAD15", "T", 76.1417, 3449.9606, 0],
[40, "PAD15", "T", 76.1417, 3449.9606, 0],
[41, "PAD16", "T", 76.1417, 3849.9606, 0],
[42, "PAD16", "T", 76.1417, 3849.9606, 0],
[43, "PAD17", "T", 76.1417, 3949.9606, 0],
[44, "PAD17", "T", 76.1417, 3949.9606, 0],
[45, "PAD18", "T", 76.1417, 4049.9606, 0],
[46, "PAD18", "T", 76.1417, 4049.9606, 0],
[47, "PAD19", "T", 76.1417, 4149.9606, 0],
[48, "PAD19", "T", 76.1417, 4149.9606, 0],
[49, "PAD20", "T", 76.1417, 4249.9606, 0],
[50, "PAD20", "T", 76.1417, 4249.9606, 0],
[51, "PAD21", "T", -108.8583, 2949.9606, 0],
[52, "PAD21", "T", -108.8583, 2949.9606, 0],
[53, "PAD22", "T", -108.8583, 3149.9606, 0],
[54, "PAD22", "T", -108.8583, 3149.9606, 0],
[55, "PAD23", "T", -108.8583, 3249.9606, 0],
[56, "PAD23", "T", -108.8583, 3249.9606, 0],
[57, "PAD24", "T", -108.8583, 3349.9606, 0],
[58, "PAD24", "T", -108.8583, 3349.9606, 0],
[59, "PAD25", "T", -108.8583, 3449.9606, 0],
[60, "PAD25", "T", -108.8583, 3449.9606, 0],
[61, "PAD26", "T", -108.8583, 3549.9606, 0],
[62, "PAD26", "T", -108.8583, 3549.9606, 0],
[63, "PAD27", "T", -108.8583, 3649.9606, 0],
[64, "PAD27", "T", -108.8583, 3649.9606, 0],
[65, "PAD28", "T", -108.8583, 3749.9606, 0],
[66, "PAD28", "T", -108.8583, 3749.9606, 0],
[67, "PAD29", "T", 766.1417, 2114.9606, 0],
[68, "PAD29", "T", 766.1417, 2114.9606, 0],
[69, "PAD30", "T", 866.1417, 2114.9606, 0],
[70, "PAD30", "T", 866.1417, 2114.9606, 0],
[71, "PAD31", "T", 766.1417, 2014.9606, 0],
[72, "PAD31", "T", 766.1417, 2014.9606, 0],
[73, "PAD32", "T", 866.1417, 2014.9606, 0],
[74, "PAD32", "T", 866.1417, 2014.9606, 0],
[75, "PAD33", "T", 766.1417, 1914.9606, 0],
[76, "PAD33", "T", 766.1417, 1914.9606, 0],
[77, "PAD34", "T", 866.1417, 1914.9606, 0],
[78, "PAD34", "T", 866.1417, 1914.9606, 0],
[79, "PAD35", "T", 866.1417, 1814.9606, 0],
[80, "PAD35", "T", 866.1417, 1814.9606, 0],
[81, "PAD36", "T", 766.1417, 1614.9606, 0],
[82, "PAD36", "T", 766.1417, 1614.9606, 0],
[83, "PAD37", "T", 866.1417, 1514.9606, 0],
[84, "PAD37", "T", 866.1417, 1514.9606, 0],
[85, "PAD38", "T", 766.1417, 1414.9606, 0],
[86, "PAD38", "T", 766.1417, 1414.9606, 0],
[87, "PAD39", "T", 866.1417, 1414.9606, 0],
[88, "PAD39", "T", 866.1417, 1414.9606, 0],
[89, "PAD40", "T", 766.1417, 1314.9606, 0],
[90, "PAD40", "T", 766.1417, 1314.9606, 0],
[91, "PAD41", "T", 866.1417, 1314.9606, 0],
[92, "PAD41", "T", 866.1417, 1314.9606, 0],
[93, "PAD42", "T", 766.1417, 1214.9606, 0],
[94, "PAD42", "T", 766.1417, 1214.9606, 0],
[95, "PAD43", "T", 766.1417, 1114.9606, 0],
[96, "PAD43", "T", 766.1417, 1114.9606, 0],
[97, "PAD44", "T", 866.1417, 1114.9606, 0],
[98, "PAD44", "T", 866.1417, 1114.9606, 0],
[99, "PAD45", "T", 766.1417, 1014.9606, 0],
[100, "PAD45", "T", 766.1417, 1014.9606, 0],
[101, "PAD46", "T", 866.1417, 1014.9606, 0],
[102, "PAD46", "T", 866.1417, 1014.9606, 0],
[103, "PAD47", "T", 766.1417, 914.9606, 0],
[104, "PAD47", "T", 766.1417, 914.9606, 0],
[105, "PAD48", "T", 866.1417, 914.9606, 0],
[106, "PAD48", "T", 866.1417, 914.9606, 0],
[107, "PAD49", "T", 766.1417, 814.9606, 0],
[108, "PAD49", "T", 766.1417, 814.9606, 0],
[109, "PAD50", "T", 866.1417, 814.9606, 0],
[110, "PAD50", "T", 866.1417, 814.9606, 0],
[111, "PAD51", "T", 766.1417, 714.9606, 0],
[112, "PAD51", "T", 766.1417, 714.9606, 0],
[113, "PAD52", "T", 866.1417, 714.9606, 0],
[114, "PAD52", "T", 866.1417, 714.9606, 0],
[115, "PAD53", "T", 766.1417, 614.9606, 0],
[116, "PAD53", "T", 766.1417, 614.9606, 0],
[117, "PAD54", "T", 866.1417, 614.9606, 0],
[118, "PAD54", "T", 866.1417, 614.9606, 0],
[119, "PAD55", "T", 766.1417, 514.9606, 0],
[120, "PAD55", "T", 766.1417, 514.9606, 0],
[121, "PAD56", "T", 866.1417, 514.9606, 0],
[122, "PAD56", "T", 866.1417, 514.9606, 0],
[123, "PAD57", "T", 766.1417, 414.9606, 0],
[124, "PAD57", "T", 766.1417, 414.9606, 0],
[125, "PAD58", "T", 866.1417, 414.9606, 0],
[126, "PAD58", "T", 866.1417, 414.9606, 0],
[127, "PAD59", "T", 766.1417, 314.9606, 0],
[128, "PAD59", "T", 766.1417, 314.9606, 0],
[129, "PAD60", "T", 866.1417, 314.9606, 0],
[130, "PAD60", "T", 866.1417, 314.9606, 0],
[131, "PAD61", "T", 766.1417, 214.9606, 0],
[132, "PAD61", "T", 766.1417, 214.9606, 0],
[133, "PAD62", "T", 866.1417, 214.9606, 0],
[134, "PAD62", "T", 866.1417, 214.9606, 0],
[135, "PAD63", "T", 241.1417, 214.9606, 0],
[136, "PAD63", "T", 241.1417, 214.9606, 0],
[137, "PAD64", "T", 241.1417, 314.9606, 0],
[138, "PAD64", "T", 241.1417, 314.9606, 0],
[139, "PAD65", "T", 141.1417, 314.9606, 0],
[140, "PAD65", "T", 141.1417, 314.9606, 0],
[141, "PAD66", "T", 241.1417, 414.9606, 0],
[142, "PAD66", "T", 241.1417, 414.9606, 0],
[143, "PAD67", "T", 141.1417, 414.9606, 0],
[144, "PAD67", "T", 141.1417, 414.9606, 0],
[145, "PAD68", "T", 241.1417, 514.9606, 0],
[146, "PAD68", "T", 241.1417, 514.9606, 0],
[147, "PAD69", "T", 141.1417, 514.9606, 0],
[148, "PAD69", "T", 141.1417, 514.9606, 0],
[149, "PAD70", "T", 241.1417, 614.9606, 0],
[150, "PAD70", "T", 241.1417, 614.9606, 0],
[151, "PAD71", "T", 141.1417, 614.9606, 0],
[152, "PAD71", "T", 141.1417, 614.9606, 0],
[153, "PAD72", "T", 241.1417, 714.9606, 0],
[154, "PAD72", "T", 241.1417, 714.9606, 0],
[155, "PAD73", "T", 141.1417, 714.9606, 0],
[156, "PAD73", "T", 141.1417, 714.9606, 0],
[157, "PAD74", "T", 241.1417, 814.9606, 0],
[158, "PAD74", "T", 241.1417, 814.9606, 0],
[159, "PAD75", "T", 141.1417, 814.9606, 0],
[160, "PAD75", "T", 141.1417, 814.9606, 0],
[161, "PAD76", "T", 241.1417, 914.9606, 0],
[162, "PAD76", "T", 241.1417, 914.9606, 0],
[163, "PAD77", "T", 141.1417, 914.9606, 0],
[164, "PAD77", "T", 141.1417, 914.9606, 0],
[165, "PAD78", "T", 241.1417, 1014.9606, 0],
[166, "PAD78", "T", 241.1417, 1014.9606, 0],
[167, "PAD79", "T", 141.1417, 1014.9606, 0],
[168, "PAD79", "T", 141.1417, 1014.9606, 0],
[169, "PAD80", "T", 241.1417, 1114.9606, 0],
[170, "PAD80", "T", 241.1417, 1114.9606, 0],
[171, "PAD81", "T", 141.1417, 1114.9606, 0],
[172, "PAD81", "T", 141.1417, 1114.9606, 0],
[173, "PAD82", "T", 141.1417, 1214.9606, 0],
[174, "PAD82", "T", 141.1417, 1214.9606, 0],
[175, "PAD83", "T", 241.1417, 1314.9606, 0],
[176, "PAD83", "T", 241.1417, 1314.9606, 0],
[177, "PAD84", "T", 141.1417, 1314.9606, 0],
[178, "PAD84", "T", 141.1417, 1314.9606, 0],
[179, "PAD85", "T", 241.1417, 1414.9606, 0],
[180, "PAD85", "T", 241.1417, 1414.9606, 0],
[181, "PAD86", "T", 141.1417, 1414.9606, 0],
[182, "PAD86", "T", 141.1417, 1414.9606, 0],
[183, "PAD87", "T", 241.1417, 1514.9606, 0],
[184, "PAD87", "T", 241.1417, 1514.9606, 0],
[185, "PAD88", "T", 141.1417, 1614.9606, 0],
[186, "PAD88", "T", 141.1417, 1614.9606, 0],
[187, "PAD89", "T", 241.1417, 1814.9606, 0],
[188, "PAD89", "T", 241.1417, 1814.9606, 0],
[189, "PAD90", "T", 241.1417, 1914.9606, 0],
[190, "PAD90", "T", 241.1417, 1914.9606, 0],
[191, "PAD91", "T", 141.1417, 1914.9606, 0],
[192, "PAD91", "T", 141.1417, 1914.9606, 0],
[193, "PAD92", "T", 241.1417, 2014.9606, 0],
[194, "PAD92", "T", 241.1417, 2014.9606, 0],
[195, "PAD93", "T", 141.1417, 2014.9606, 0],
[196, "PAD93", "T", 141.1417, 2014.9606, 0],
[197, "PAD94", "T", 241.1417, 2114.9606, 0],
[198, "PAD94", "T", 241.1417, 2114.9606, 0],
[199, "PAD95", "T", 141.1417, 2114.9606, 0],
[200, "PAD95", "T", 141.1417, 2114.9606, 0],
[201, "PAD96", "T", 1507.6417, 2365.3606, 0],
[202, "PAD96", "T", 1507.6417, 2365.3606, 0],
[203, "PAD97", "T", 1407.6417, 2365.3606, 0],
[204, "PAD97", "T", 1407.6417, 2365.3606, 0],
[205, "PAD98", "T", 1507.6417, 2465.3606, 0],
[206, "PAD98", "T", 1507.6417, 2465.3606, 0],
[207, "PAD99", "T", 1407.6417, 2465.3606, 0],
[208, "PAD99", "T", 1407.6417, 2465.3606, 0],
[209, "PAD100", "T", 1507.6417, 2565.3606, 0],
[210, "PAD100", "T", 1507.6417, 2565.3606, 0],
[211, "PAD101", "T", 1407.6417, 2565.3606, 0],
[212, "PAD101", "T", 1407.6417, 2565.3606, 0],
[213, "PAD102", "T", 1507.6417, 2665.3606, 0],
[214, "PAD102", "T", 1507.6417, 2665.3606, 0],
[215, "PAD103", "T", 1407.6417, 2665.3606, 0],
[216, "PAD103", "T", 1407.6417, 2665.3606, 0],
[217, "PAD104", "T", 1507.6417, 2765.3606, 0],
[218, "PAD104", "T", 1507.6417, 2765.3606, 0],
[219, "PAD105", "T", 1407.6417, 2765.3606, 0],
[220, "PAD105", "T", 1407.6417, 2765.3606, 0],
[221, "PAD106", "T", 1507.6417, 2865.3606, 0],
[222, "PAD106", "T", 1507.6417, 2865.3606, 0],
[223, "PAD107", "T", 1407.6417, 2865.3606, 0],
[224, "PAD107", "T", 1407.6417, 2865.3606, 0],
[225, "PAD108", "T", 1507.6417, 2965.3606, 0],
[226, "PAD108", "T", 1507.6417, 2965.3606, 0],
[227, "PAD109", "T", 1407.6417, 2965.3606, 0],
[228, "PAD109", "T", 1407.6417, 2965.3606, 0],
[229, "PAD110", "T", 1507.6417, 3065.3606, 0],
[230, "PAD110", "T", 1507.6417, 3065.3606, 0],
[231, "PAD111", "T", 1407.6417, 3065.3606, 0],
[232, "PAD111", "T", 1407.6417, 3065.3606, 0],
[233, "PAD112", "T", 1507.6417, 3165.3606, 0],
[234, "PAD112", "T", 1507.6417, 3165.3606, 0],
[235, "PAD113", "T", 1407.6417, 3165.3606, 0],
[236, "PAD113", "T", 1407.6417, 3165.3606, 0],
[237, "PAD114", "T", 1507.6417, 3265.3606, 0],
[238, "PAD114", "T", 1507.6417, 3265.3606, 0],
[239, "PAD115", "T", 1407.6417, 3265.3606, 0],
[240, "PAD115", "T", 1407.6417, 3265.3606, 0],
[241, "PAD116", "T", 1397.6378, 3787.0078, 0],
[242, "PAD116", "T", 1397.6378, 3787.0078, 0],
[243, "PAD117", "T", 1297.6378, 3787.0078, 0],
[244, "PAD117", "T", 1297.6378, 3787.0078, 0],
[245, "PAD118", "T", 1397.6378, 3887.0078, 0],
[246, "PAD118", "T", 1397.6378, 3887.0078, 0],
[247, "PAD119", "T", 1297.6378, 3887.0078, 0],
[248, "PAD119", "T", 1297.6378, 3887.0078, 0],
[249, "PAD120", "T", 1397.6378, 3987.0078, 0],
[250, "PAD120", "T", 1397.6378, 3987.0078, 0],
[251, "PAD121", "T", 1297.6378, 3987.0078, 0],
[252, "PAD121", "T", 1297.6378, 3987.0078, 0],
[253, "PAD122", "T", 1397.6378, 4087.0078, 0],
[254, "PAD122", "T", 1397.6378, 4087.0078, 0],
[255, "PAD123", "T", 1297.6378, 4087.0078, 0],
[256, "PAD123", "T", 1297.6378, 4087.0078, 0],
[257, "PAD124", "T", 1397.6378, 4187.0078, 0],
[258, "PAD124", "T", 1397.6378, 4187.0078, 0],
[259, "PAD125", "T", 823.4417, 3940.1606, 0],
[260, "PAD125", "T", 823.4417, 3940.1606, 0],
[261, "PAD126", "T", 823.4417, 3840.1606, 0],
[262, "PAD126", "T", 823.4417, 3840.1606, 0],
[263, "PAD127", "T", 523.4417, 3840.1606, 0],
[264, "PAD127", "T", 523.4417, 3840.1606, 0],
[265, "PAD128", "T", 423.4417, 3940.1606, 0],
[266, "PAD128", "T", 423.4417, 3940.1606, 0],
[267, "PAD129", "T", 1791.3386, 3787.0078, 0],
[268, "PAD129", "T", 1791.3386, 3787.0078, 0],
[269, "PAD130", "T", 1691.3386, 3787.0078, 0],
[270, "PAD130", "T", 1691.3386, 3787.0078, 0],
[271, "PAD131", "T", 1791.3386, 3887.0078, 0],
[272, "PAD131", "T", 1791.3386, 3887.0078, 0],
[273, "PAD132", "T", 1691.3386, 3887.0078, 0],
[274, "PAD132", "T", 1691.3386, 3887.0078, 0],
[275, "PAD133", "T", 1791.3386, 3987.0078, 0],
[276, "PAD133", "T", 1791.3386, 3987.0078, 0],
[277, "PAD134", "T", 1691.3386, 3987.0078, 0],
[278, "PAD134", "T", 1691.3386, 3987.0078, 0],
[279, "PAD135", "T", 1791.3386, 4087.0078, 0],
[280, "PAD135", "T", 1791.3386, 4087.0078, 0],
[281, "PAD136", "T", 1691.3386, 4087.0078, 0],
[282, "PAD136", "T", 1691.3386, 4087.0078, 0],
[283, "PAD137", "T", 1791.3386, 4187.0078, 0],
[284, "PAD137", "T", 1791.3386, 4187.0078, 0],
[285, "PAD138", "T", -908.8583, 4244.9606, 0],
[286, "PAD138", "T", -908.8583, 4244.9606, 0],
[287, "PAD139", "T", -808.8583, 4244.9606, 0],
[288, "PAD139", "T", -808.8583, 4244.9606, 0],
[289, "PAD140", "T", -708.8583, 4244.9606, 0],
[290, "PAD140", "T", -708.8583, 4244.9606, 0],
[291, "PAD141", "T", -608.8583, 4244.9606, 0],
[292, "PAD141", "T", -608.8583, 4244.9606, 0],
[293, "PAD142", "T", 623.7817, 2454.9606, 0],
[294, "PAD143", "T", 828.5017, 2454.9606, 0],
[295, "PAD144", "T", 621.1417, 1657.5206, 0],
[296, "PAD144", "T", 621.1417, 1657.5206, 0],
[297, "PAD145", "T", 621.1417, 1315.0006, 0],
[298, "PAD145", "T", 621.1417, 1315.0006, 0]
]
},
"pins": {
"fields": ["PIN_NO", "PIN_NAME", "PIN_X", "PIN_Y", "LAYER", "PIN_TYPE", "NET_NAME", "NET_TYPE", "PAD_SHAPE", "PAD_SIZEX", "PAD_SIZEY", "HOLE_SIZE", "HOLE_LEN", "PAD_ANGLE"],
"rows": [
[1, "C65_1", 623.7817, 2454.9606, "T", "SMD", "VCC5", "", "R", 74.803, 93.543, 0, 0, 180],
[2, "C65_2", 828.5017, 2454.9606, "T", "SMD", "GND", "GND", "R", 74.803, 93.543, 0, 0, 180],
[3, "J1_1", 471.1417, 1514.9606, "T", "DIP", "VCC5", "", "R", 65, 65, 38, 38, 270],
[4, "J1_1", 471.1417, 1514.9606, "T", "DIP", "VCC5", "", "R", 65, 65, 38, 38, 270],
[5, "J1_2", 471.1417, 1414.9606, "T", "DIP", "IDE5V", "", "C", 65, 65, 38, 38, 270],
[6, "J1_2", 471.1417, 1414.9606, "T", "DIP", "IDE5V", "", "C", 65, 65, 38, 38, 270],
[7, "R1_1", 268.1117, 3852.4908, "B", "SMD", "ZIFI_RX", "", "R", 44.588, 54.213, 0, 0, 180],
[8, "R1_2", 189.3717, 3852.4908, "B", "SMD", "$1N45", "", "R", 44.588, 54.213, 0, 0, 180],
[9, "R2_1", 216.3717, 3250.4908, "B", "SMD", "ESP_SCK", "", "R", 44.588, 54.213, 0, 0, 0],
[10, "R2_2", 295.1117, 3250.4908, "B", "SMD", "$1N192", "", "R", 44.588, 54.213, 0, 0, 0],
[11, "U1_1", 76.1417, 4949.9606, "T", "DIP", "NET_1", "", "R", 75, 55, 36, 36, 0],
[12, "U1_1", 76.1417, 4949.9606, "T", "DIP", "NET_1", "", "R", 75, 55, 36, 36, 0],
[13, "U1_2", 76.1417, 4849.9606, "T", "DIP", "NET_2", "", "O", 75, 55, 36, 36, 0],
[14, "U1_2", 76.1417, 4849.9606, "T", "DIP", "NET_2", "", "O", 75, 55, 36, 36, 0],
[15, "U1_3", 76.1417, 4749.9606, "T", "DIP", "NET_3", "", "O", 75, 55, 36, 36, 0],
[16, "U1_3", 76.1417, 4749.9606, "T", "DIP", "NET_3", "", "O", 75, 55, 36, 36, 0],
[17, "U1_4", 76.1417, 4649.9606, "T", "DIP", "NET_4", "", "O", 75, 55, 36, 36, 0],
[18, "U1_4", 76.1417, 4649.9606, "T", "DIP", "NET_4", "", "O", 75, 55, 36, 36, 0],
[19, "U1_5", 76.1417, 4549.9606, "T", "DIP", "NET_5", "", "O", 75, 55, 36, 36, 0],
[20, "U1_5", 76.1417, 4549.9606, "T", "DIP", "NET_5", "", "O", 75, 55, 36, 36, 0],
[21, "U1_6", 76.1417, 4449.9606, "T", "DIP", "NET_6", "", "O", 75, 55, 36, 36, 0],
[22, "U1_6", 76.1417, 4449.9606, "T", "DIP", "NET_6", "", "O", 75, 55, 36, 36, 0],
[23, "U1_7", 76.1417, 4349.9606, "T", "DIP", "NET_7", "", "O", 75, 55, 36, 36, 0],
[24, "U1_7", 76.1417, 4349.9606, "T", "DIP", "NET_7", "", "O", 75, 55, 36, 36, 0],
[25, "U1_8", 76.1417, 4249.9606, "T", "DIP", "PCM_BLK", "", "O", 75, 55, 36, 36, 0],
[26, "U1_8", 76.1417, 4249.9606, "T", "DIP", "PCM_BLK", "", "O", 75, 55, 36, 36, 0],
[27, "U1_9", 76.1417, 4149.9606, "T", "DIP", "PCM_LRCK", "", "O", 75, 55, 36, 36, 0],
[28, "U1_9", 76.1417, 4149.9606, "T", "DIP", "PCM_LRCK", "", "O", 75, 55, 36, 36, 0],
[29, "U1_10", 76.1417, 4049.9606, "T", "DIP", "PCM_DIN", "", "O", 75, 55, 36, 36, 0],
[30, "U1_10", 76.1417, 4049.9606, "T", "DIP", "PCM_DIN", "", "O", 75, 55, 36, 36, 0],
[31, "U1_11", 76.1417, 3949.9606, "T", "DIP", "ZIFI_TX", "", "O", 75, 55, 36, 36, 0],
[32, "U1_11", 76.1417, 3949.9606, "T", "DIP", "ZIFI_TX", "", "O", 75, 55, 36, 36, 0],
[33, "U1_12", 76.1417, 3849.9606, "T", "DIP", "$1N45", "", "O", 75, 55, 36, 36, 0],
[34, "U1_12", 76.1417, 3849.9606, "T", "DIP", "$1N45", "", "O", 75, 55, 36, 36, 0],
[35, "U1_13", 76.1417, 3749.9606, "T", "DIP", "NET_8", "", "O", 75, 55, 36, 36, 0],
[36, "U1_13", 76.1417, 3749.9606, "T", "DIP", "NET_8", "", "O", 75, 55, 36, 36, 0],
[37, "U1_14", 76.1417, 3649.9606, "T", "DIP", "NET_9", "", "O", 75, 55, 36, 36, 0],
[38, "U1_14", 76.1417, 3649.9606, "T", "DIP", "NET_9", "", "O", 75, 55, 36, 36, 0],
[39, "U1_15", 76.1417, 3549.9606, "T", "DIP", "NET_10", "", "O", 75, 55, 36, 36, 0],
[40, "U1_15", 76.1417, 3549.9606, "T", "DIP", "NET_10", "", "O", 75, 55, 36, 36, 0],
[41, "U1_16", 76.1417, 3449.9606, "T", "DIP", "ESP_CS", "", "O", 75, 55, 36, 36, 0],
[42, "U1_16", 76.1417, 3449.9606, "T", "DIP", "ESP_CS", "", "O", 75, 55, 36, 36, 0],
[43, "U1_17", 76.1417, 3349.9606, "T", "DIP", "ESP_MISO", "", "O", 75, 55, 36, 36, 0],
[44, "U1_17", 76.1417, 3349.9606, "T", "DIP", "ESP_MISO", "", "O", 75, 55, 36, 36, 0],
[45, "U1_18", 76.1417, 3249.9606, "T", "DIP", "ESP_SCK", "", "O", 75, 55, 36, 36, 0],
[46, "U1_18", 76.1417, 3249.9606, "T", "DIP", "ESP_SCK", "", "O", 75, 55, 36, 36, 0],
[47, "U1_19", 76.1417, 3149.9606, "T", "DIP", "ESP_MOSI", "", "O", 75, 55, 36, 36, 0],
[48, "U1_19", 76.1417, 3149.9606, "T", "DIP", "ESP_MOSI", "", "O", 75, 55, 36, 36, 0],
[49, "U1_20", 76.1417, 3049.9606, "T", "DIP", "NET_11", "", "O", 75, 55, 36, 36, 0],
[50, "U1_20", 76.1417, 3049.9606, "T", "DIP", "NET_11", "", "O", 75, 55, 36, 36, 0],
[51, "U1_21", 76.1417, 2949.9606, "T", "DIP", "VCC5", "", "O", 75, 55, 36, 36, 0],
[52, "U1_21", 76.1417, 2949.9606, "T", "DIP", "VCC5", "", "O", 75, 55, 36, 36, 0],
[53, "U1_22", 76.1417, 2849.9606, "T", "DIP", "GND", "GND", "O", 75, 55, 36, 36, 0],
[54, "U1_22", 76.1417, 2849.9606, "T", "DIP", "GND", "GND", "O", 75, 55, 36, 36, 0],
[55, "U1_23", 1076.1417, 2849.9606, "T", "DIP", "GND", "GND", "O", 75, 55, 36, 36, 0],
[56, "U1_23", 1076.1417, 2849.9606, "T", "DIP", "GND", "GND", "O", 75, 55, 36, 36, 0],
[57, "U1_24", 1076.1417, 2949.9606, "T", "DIP", "GND", "GND", "O", 75, 55, 36, 36, 0],
[58, "U1_24", 1076.1417, 2949.9606, "T", "DIP", "GND", "GND", "O", 75, 55, 36, 36, 0],
[59, "U1_25", 1076.1417, 3049.9606, "T", "DIP", "NET_12", "", "O", 75, 55, 36, 36, 0],
[60, "U1_25", 1076.1417, 3049.9606, "T", "DIP", "NET_12", "", "O", 75, 55, 36, 36, 0],
[61, "U1_26", 1076.1417, 3149.9606, "T", "DIP", "NET_13", "", "O", 75, 55, 36, 36, 0],
[62, "U1_26", 1076.1417, 3149.9606, "T", "DIP", "NET_13", "", "O", 75, 55, 36, 36, 0],
[63, "U1_27", 1076.1417, 3249.9606, "T", "DIP", "NET_14", "", "O", 75, 55, 36, 36, 0],
[64, "U1_27", 1076.1417, 3249.9606, "T", "DIP", "NET_14", "", "O", 75, 55, 36, 36, 0],
[65, "U1_28", 1076.1417, 3349.9606, "T", "DIP", "NET_15", "", "O", 75, 55, 36, 36, 0],
[66, "U1_28", 1076.1417, 3349.9606, "T", "DIP", "NET_15", "", "O", 75, 55, 36, 36, 0],
[67, "U1_29", 1076.1417, 3449.9606, "T", "DIP", "NET_16", "", "O", 75, 55, 36, 36, 0],
[68, "U1_29", 1076.1417, 3449.9606, "T", "DIP", "NET_16", "", "O", 75, 55, 36, 36, 0],
[69, "U1_30", 1076.1417, 3549.9606, "T", "DIP", "NET_17", "", "O", 75, 55, 36, 36, 0],
[70, "U1_30", 1076.1417, 3549.9606, "T", "DIP", "NET_17", "", "O", 75, 55, 36, 36, 0],
[71, "U1_31", 1076.1417, 3649.9606, "T", "DIP", "NET_18", "", "O", 75, 55, 36, 36, 0],
[72, "U1_31", 1076.1417, 3649.9606, "T", "DIP", "NET_18", "", "O", 75, 55, 36, 36, 0],
[73, "U1_32", 1076.1417, 3749.9606, "T", "DIP", "NET_19", "", "O", 75, 55, 36, 36, 0],
[74, "U1_32", 1076.1417, 3749.9606, "T", "DIP", "NET_19", "", "O", 75, 55, 36, 36, 0],
[75, "U1_33", 1076.1417, 3849.9606, "T", "DIP", "NET_20", "", "O", 75, 55, 36, 36, 0],
[76, "U1_33", 1076.1417, 3849.9606, "T", "DIP", "NET_20", "", "O", 75, 55, 36, 36, 0],
[77, "U1_34", 1076.1417, 3949.9606, "T", "DIP", "NET_21", "", "O", 75, 55, 36, 36, 0],
[78, "U1_34", 1076.1417, 3949.9606, "T", "DIP", "NET_21", "", "O", 75, 55, 36, 36, 0],
[79, "U1_35", 1076.1417, 4049.9606, "T", "DIP", "NET_22", "", "O", 75, 55, 36, 36, 0],
[80, "U1_35", 1076.1417, 4049.9606, "T", "DIP", "NET_22", "", "O", 75, 55, 36, 36, 0],
[81, "U1_36", 1076.1417, 4149.9606, "T", "DIP", "NET_23", "", "O", 75, 55, 36, 36, 0],
[82, "U1_36", 1076.1417, 4149.9606, "T", "DIP", "NET_23", "", "O", 75, 55, 36, 36, 0],
[83, "U1_37", 1076.1417, 4249.9606, "T", "DIP", "NET_24", "", "O", 75, 55, 36, 36, 0],
[84, "U1_37", 1076.1417, 4249.9606, "T", "DIP", "NET_24", "", "O", 75, 55, 36, 36, 0],
[85, "U1_38", 1076.1417, 4349.9606, "T", "DIP", "NET_25", "", "O", 75, 55, 36, 36, 0],
[86, "U1_38", 1076.1417, 4349.9606, "T", "DIP", "NET_25", "", "O", 75, 55, 36, 36, 0],
[87, "U1_39", 1076.1417, 4449.9606, "T", "DIP", "NET_26", "", "O", 75, 55, 36, 36, 0],
[88, "U1_39", 1076.1417, 4449.9606, "T", "DIP", "NET_26", "", "O", 75, 55, 36, 36, 0],
[89, "U1_40", 1076.1417, 4549.9606, "T", "DIP", "NET_27", "", "O", 75, 55, 36, 36, 0],
[90, "U1_40", 1076.1417, 4549.9606, "T", "DIP", "NET_27", "", "O", 75, 55, 36, 36, 0],
[91, "U1_41", 1076.1417, 4649.9606, "T", "DIP", "NET_28", "", "O", 75, 55, 36, 36, 0],
[92, "U1_41", 1076.1417, 4649.9606, "T", "DIP", "NET_28", "", "O", 75, 55, 36, 36, 0],
[93, "U1_42", 1076.1417, 4749.9606, "T", "DIP", "NET_29", "", "O", 75, 55, 36, 36, 0],
[94, "U1_42", 1076.1417, 4749.9606, "T", "DIP", "NET_29", "", "O", 75, 55, 36, 36, 0],
[95, "U1_43", 1076.1417, 4849.9606, "T", "DIP", "NET_30", "", "O", 75, 55, 36, 36, 0],
[96, "U1_43", 1076.1417, 4849.9606, "T", "DIP", "NET_30", "", "O", 75, 55, 36, 36, 0],
[97, "U1_44", 1076.1417, 4949.9606, "T", "DIP", "GND", "GND", "O", 75, 55, 36, 36, 0],
[98, "U1_44", 1076.1417, 4949.9606, "T", "DIP", "GND", "GND", "O", 75, 55, 36, 36, 0],
[99, "U3_1", -108.8583, 2949.9606, "T", "DIP", "VCC5", "", "C", 65, 65, 36, 36, 90],
[100, "U3_1", -108.8583, 2949.9606, "T", "DIP", "VCC5", "", "C", 65, 65, 36, 36, 90],
[101, "U3_2", -108.8583, 3049.9606, "T", "DIP", "NET_31", "", "C", 65, 65, 36, 36, 90],
[102, "U3_2", -108.8583, 3049.9606, "T", "DIP", "NET_31", "", "C", 65, 65, 36, 36, 90],
[103, "U3_3", -108.8583, 3149.9606, "T", "DIP", "GND", "GND", "C", 65, 65, 36, 36, 90],
[104, "U3_3", -108.8583, 3149.9606, "T", "DIP", "GND", "GND", "C", 65, 65, 36, 36, 90],
[105, "U3_4", -108.8583, 3249.9606, "T", "DIP", "PCM_LRCK", "", "C", 65, 65, 36, 36, 90],
[106, "U3_4", -108.8583, 3249.9606, "T", "DIP", "PCM_LRCK", "", "C", 65, 65, 36, 36, 90],
[107, "U3_5", -108.8583, 3349.9606, "T", "DIP", "PCM_DIN", "", "C", 65, 65, 36, 36, 90],
[108, "U3_5", -108.8583, 3349.9606, "T", "DIP", "PCM_DIN", "", "C", 65, 65, 36, 36, 90],
[109, "U3_6", -108.8583, 3449.9606, "T", "DIP", "PCM_BLK", "", "C", 65, 65, 36, 36, 90],
[110, "U3_6", -108.8583, 3449.9606, "T", "DIP", "PCM_BLK", "", "C", 65, 65, 36, 36, 90],
[111, "U3_7", -108.8583, 3549.9606, "T", "DIP", "L_OUT", "", "C", 65, 65, 36, 36, 90],
[112, "U3_7", -108.8583, 3549.9606, "T", "DIP", "L_OUT", "", "C", 65, 65, 36, 36, 90],
[113, "U3_8", -108.8583, 3649.9606, "T", "DIP", "AGND", "", "C", 65, 65, 36, 36, 90],
[114, "U3_8", -108.8583, 3649.9606, "T", "DIP", "AGND", "", "C", 65, 65, 36, 36, 90],
[115, "U3_9", -108.8583, 3749.9606, "T", "DIP", "R_OUT", "", "C", 65, 65, 36, 36, 90],
[116, "U3_9", -108.8583, 3749.9606, "T", "DIP", "R_OUT", "", "C", 65, 65, 36, 36, 90],
[117, "U3_10", -908.8583, 3549.9606, "T", "DIP", "NET_32", "", "C", 65, 65, 36, 36, 90],
[118, "U3_10", -908.8583, 3549.9606, "T", "DIP", "NET_32", "", "C", 65, 65, 36, 36, 90],
[119, "U3_11", -908.8583, 3449.9606, "T", "DIP", "NET_33", "", "C", 65, 65, 36, 36, 90],
[120, "U3_11", -908.8583, 3449.9606, "T", "DIP", "NET_33", "", "C", 65, 65, 36, 36, 90],
[121, "U3_12", -908.8583, 3349.9606, "T", "DIP", "NET_34", "", "C", 65, 65, 36, 36, 90],
[122, "U3_12", -908.8583, 3349.9606, "T", "DIP", "NET_34", "", "C", 65, 65, 36, 36, 90],
[123, "U3_13", -908.8583, 3249.9606, "T", "DIP", "NET_35", "", "C", 65, 65, 36, 36, 90],
[124, "U3_13", -908.8583, 3249.9606, "T", "DIP", "NET_35", "", "C", 65, 65, 36, 36, 90],
[125, "U3_14", -908.8583, 3149.9606, "T", "DIP", "NET_36", "", "C", 65, 65, 36, 36, 90],
[126, "U3_14", -908.8583, 3149.9606, "T", "DIP", "NET_36", "", "C", 65, 65, 36, 36, 90],
[127, "U3_15", -908.8583, 3049.9606, "T", "DIP", "NET_37", "", "C", 65, 65, 36, 36, 90],
[128, "U3_15", -908.8583, 3049.9606, "T", "DIP", "NET_37", "", "C", 65, 65, 36, 36, 90],
[129, "VD1_1", 621.1417, 1657.5206, "T", "DIP", "VCC5", "", "C", 70.866, 70.866, 39.37, 39.37, 270],
[130, "VD1_1", 621.1417, 1657.5206, "T", "DIP", "VCC5", "", "C", 70.866, 70.866, 39.37, 39.37, 270],
[131, "VD1_2", 621.1417, 1315.0006, "T", "DIP", "$1N233", "", "C", 70.866, 70.866, 39.37, 39.37, 270],
[132, "VD1_2", 621.1417, 1315.0006, "T", "DIP", "$1N233", "", "C", 70.866, 70.866, 39.37, 39.37, 270],
[133, "X1_1", 866.1417, 214.9606, "T", "DIP", "ESP_CS", "", "R", 62.992, 62.992, 39.37, 39.37, 90],
[134, "X1_1", 866.1417, 214.9606, "T", "DIP", "ESP_CS", "", "R", 62.992, 62.992, 39.37, 39.37, 90],
[135, "X1_2", 766.1417, 214.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[136, "X1_2", 766.1417, 214.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[137, "X1_3", 866.1417, 314.9606, "T", "DIP", "ID7", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[138, "X1_3", 866.1417, 314.9606, "T", "DIP", "ID7", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[139, "X1_4", 766.1417, 314.9606, "T", "DIP", "ID8", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[140, "X1_4", 766.1417, 314.9606, "T", "DIP", "ID8", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[141, "X1_5", 866.1417, 414.9606, "T", "DIP", "ID6", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[142, "X1_5", 866.1417, 414.9606, "T", "DIP", "ID6", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[143, "X1_6", 766.1417, 414.9606, "T", "DIP", "ID9", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[144, "X1_6", 766.1417, 414.9606, "T", "DIP", "ID9", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[145, "X1_7", 866.1417, 514.9606, "T", "DIP", "ID5", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[146, "X1_7", 866.1417, 514.9606, "T", "DIP", "ID5", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[147, "X1_8", 766.1417, 514.9606, "T", "DIP", "ID10", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[148, "X1_8", 766.1417, 514.9606, "T", "DIP", "ID10", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[149, "X1_9", 866.1417, 614.9606, "T", "DIP", "ID4", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[150, "X1_9", 866.1417, 614.9606, "T", "DIP", "ID4", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[151, "X1_10", 766.1417, 614.9606, "T", "DIP", "ID11", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[152, "X1_10", 766.1417, 614.9606, "T", "DIP", "ID11", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[153, "X1_11", 866.1417, 714.9606, "T", "DIP", "ID3", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[154, "X1_11", 866.1417, 714.9606, "T", "DIP", "ID3", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[155, "X1_12", 766.1417, 714.9606, "T", "DIP", "ID12", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[156, "X1_12", 766.1417, 714.9606, "T", "DIP", "ID12", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[157, "X1_13", 866.1417, 814.9606, "T", "DIP", "ID2", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[158, "X1_13", 866.1417, 814.9606, "T", "DIP", "ID2", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[159, "X1_14", 766.1417, 814.9606, "T", "DIP", "ID13", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[160, "X1_14", 766.1417, 814.9606, "T", "DIP", "ID13", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[161, "X1_15", 866.1417, 914.9606, "T", "DIP", "ID1", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[162, "X1_15", 866.1417, 914.9606, "T", "DIP", "ID1", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[163, "X1_16", 766.1417, 914.9606, "T", "DIP", "ID14", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[164, "X1_16", 766.1417, 914.9606, "T", "DIP", "ID14", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[165, "X1_17", 866.1417, 1014.9606, "T", "DIP", "ID0", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[166, "X1_17", 866.1417, 1014.9606, "T", "DIP", "ID0", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[167, "X1_18", 766.1417, 1014.9606, "T", "DIP", "ID15", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[168, "X1_18", 766.1417, 1014.9606, "T", "DIP", "ID15", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[169, "X1_19", 866.1417, 1114.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[170, "X1_19", 866.1417, 1114.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[171, "X1_20", 766.1417, 1114.9606, "T", "DIP", "$1N233", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[172, "X1_20", 766.1417, 1114.9606, "T", "DIP", "$1N233", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[173, "X1_21", 866.1417, 1214.9606, "T", "DIP", "NET_38", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[174, "X1_21", 866.1417, 1214.9606, "T", "DIP", "NET_38", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[175, "X1_22", 766.1417, 1214.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[176, "X1_22", 766.1417, 1214.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[177, "X1_23", 866.1417, 1314.9606, "T", "DIP", "IWR_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[178, "X1_23", 866.1417, 1314.9606, "T", "DIP", "IWR_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[179, "X1_24", 766.1417, 1314.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[180, "X1_24", 766.1417, 1314.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[181, "X1_25", 866.1417, 1414.9606, "T", "DIP", "IRD_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[182, "X1_25", 866.1417, 1414.9606, "T", "DIP", "IRD_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[183, "X1_26", 766.1417, 1414.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[184, "X1_26", 766.1417, 1414.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[185, "X1_27", 866.1417, 1514.9606, "T", "DIP", "ESP_MISO", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[186, "X1_27", 866.1417, 1514.9606, "T", "DIP", "ESP_MISO", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[187, "X1_28", 766.1417, 1514.9606, "T", "DIP", "NET_39", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[188, "X1_28", 766.1417, 1514.9606, "T", "DIP", "NET_39", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[189, "X1_29", 866.1417, 1614.9606, "T", "DIP", "NET_40", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[190, "X1_29", 866.1417, 1614.9606, "T", "DIP", "NET_40", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[191, "X1_30", 766.1417, 1614.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[192, "X1_30", 766.1417, 1614.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[193, "X1_31", 866.1417, 1714.9606, "T", "DIP", "NET_41", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[194, "X1_31", 866.1417, 1714.9606, "T", "DIP", "NET_41", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[195, "X1_32", 766.1417, 1714.9606, "T", "DIP", "NET_42", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[196, "X1_32", 766.1417, 1714.9606, "T", "DIP", "NET_42", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[197, "X1_33", 866.1417, 1814.9606, "T", "DIP", "ESP_MOSI", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[198, "X1_33", 866.1417, 1814.9606, "T", "DIP", "ESP_MOSI", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[199, "X1_34", 766.1417, 1814.9606, "T", "DIP", "NET_43", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[200, "X1_34", 766.1417, 1814.9606, "T", "DIP", "NET_43", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[201, "X1_35", 866.1417, 1914.9606, "T", "DIP", "$1N192", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[202, "X1_35", 866.1417, 1914.9606, "T", "DIP", "$1N192", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[203, "X1_36", 766.1417, 1914.9606, "T", "DIP", "IA2", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[204, "X1_36", 766.1417, 1914.9606, "T", "DIP", "IA2", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[205, "X1_37", 866.1417, 2014.9606, "T", "DIP", "ICS0_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[206, "X1_37", 866.1417, 2014.9606, "T", "DIP", "ICS0_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[207, "X1_38", 766.1417, 2014.9606, "T", "DIP", "ICS1_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[208, "X1_38", 766.1417, 2014.9606, "T", "DIP", "ICS1_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[209, "X1_39", 866.1417, 2114.9606, "T", "DIP", "DASP_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[210, "X1_39", 866.1417, 2114.9606, "T", "DIP", "DASP_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[211, "X1_40", 766.1417, 2114.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[212, "X1_40", 766.1417, 2114.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[213, "X2_1", 241.1417, 214.9606, "T", "DIP", "ESP_CS", "", "R", 62.992, 62.992, 36, 36, 90],
[214, "X2_1", 241.1417, 214.9606, "T", "DIP", "ESP_CS", "", "R", 62.992, 62.992, 36, 36, 90],
[215, "X2_2", 141.1417, 214.9606, "T", "DIP", "NET_44", "", "C", 62.992, 62.992, 36, 36, 90],
[216, "X2_2", 141.1417, 214.9606, "T", "DIP", "NET_44", "", "C", 62.992, 62.992, 36, 36, 90],
[217, "X2_3", 241.1417, 314.9606, "T", "DIP", "ID7", "", "C", 62.992, 62.992, 36, 36, 90],
[218, "X2_3", 241.1417, 314.9606, "T", "DIP", "ID7", "", "C", 62.992, 62.992, 36, 36, 90],
[219, "X2_4", 141.1417, 314.9606, "T", "DIP", "ID8", "", "C", 62.992, 62.992, 36, 36, 90],
[220, "X2_4", 141.1417, 314.9606, "T", "DIP", "ID8", "", "C", 62.992, 62.992, 36, 36, 90],
[221, "X2_5", 241.1417, 414.9606, "T", "DIP", "ID6", "", "C", 62.992, 62.992, 36, 36, 90],
[222, "X2_5", 241.1417, 414.9606, "T", "DIP", "ID6", "", "C", 62.992, 62.992, 36, 36, 90],
[223, "X2_6", 141.1417, 414.9606, "T", "DIP", "ID9", "", "C", 62.992, 62.992, 36, 36, 90],
[224, "X2_6", 141.1417, 414.9606, "T", "DIP", "ID9", "", "C", 62.992, 62.992, 36, 36, 90],
[225, "X2_7", 241.1417, 514.9606, "T", "DIP", "ID5", "", "C", 62.992, 62.992, 36, 36, 90],
[226, "X2_7", 241.1417, 514.9606, "T", "DIP", "ID5", "", "C", 62.992, 62.992, 36, 36, 90],
[227, "X2_8", 141.1417, 514.9606, "T", "DIP", "ID10", "", "C", 62.992, 62.992, 36, 36, 90],
[228, "X2_8", 141.1417, 514.9606, "T", "DIP", "ID10", "", "C", 62.992, 62.992, 36, 36, 90],
[229, "X2_9", 241.1417, 614.9606, "T", "DIP", "ID4", "", "C", 62.992, 62.992, 36, 36, 90],
[230, "X2_9", 241.1417, 614.9606, "T", "DIP", "ID4", "", "C", 62.992, 62.992, 36, 36, 90],
[231, "X2_10", 141.1417, 614.9606, "T", "DIP", "ID11", "", "C", 62.992, 62.992, 36, 36, 90],
[232, "X2_10", 141.1417, 614.9606, "T", "DIP", "ID11", "", "C", 62.992, 62.992, 36, 36, 90],
[233, "X2_11", 241.1417, 714.9606, "T", "DIP", "ID3", "", "C", 62.992, 62.992, 36, 36, 90],
[234, "X2_11", 241.1417, 714.9606, "T", "DIP", "ID3", "", "C", 62.992, 62.992, 36, 36, 90],
[235, "X2_12", 141.1417, 714.9606, "T", "DIP", "ID12", "", "C", 62.992, 62.992, 36, 36, 90],
[236, "X2_12", 141.1417, 714.9606, "T", "DIP", "ID12", "", "C", 62.992, 62.992, 36, 36, 90],
[237, "X2_13", 241.1417, 814.9606, "T", "DIP", "ID2", "", "C", 62.992, 62.992, 36, 36, 90],
[238, "X2_13", 241.1417, 814.9606, "T", "DIP", "ID2", "", "C", 62.992, 62.992, 36, 36, 90],
[239, "X2_14", 141.1417, 814.9606, "T", "DIP", "ID13", "", "C", 62.992, 62.992, 36, 36, 90],
[240, "X2_14", 141.1417, 814.9606, "T", "DIP", "ID13", "", "C", 62.992, 62.992, 36, 36, 90],
[241, "X2_15", 241.1417, 914.9606, "T", "DIP", "ID1", "", "C", 62.992, 62.992, 36, 36, 90],
[242, "X2_15", 241.1417, 914.9606, "T", "DIP", "ID1", "", "C", 62.992, 62.992, 36, 36, 90],
[243, "X2_16", 141.1417, 914.9606, "T", "DIP", "ID14", "", "C", 62.992, 62.992, 36, 36, 90],
[244, "X2_16", 141.1417, 914.9606, "T", "DIP", "ID14", "", "C", 62.992, 62.992, 36, 36, 90],
[245, "X2_17", 241.1417, 1014.9606, "T", "DIP", "ID0", "", "C", 62.992, 62.992, 36, 36, 90],
[246, "X2_17", 241.1417, 1014.9606, "T", "DIP", "ID0", "", "C", 62.992, 62.992, 36, 36, 90],
[247, "X2_18", 141.1417, 1014.9606, "T", "DIP", "ID15", "", "C", 62.992, 62.992, 36, 36, 90],
[248, "X2_18", 141.1417, 1014.9606, "T", "DIP", "ID15", "", "C", 62.992, 62.992, 36, 36, 90],
[249, "X2_19", 241.1417, 1114.9606, "T", "DIP", "GND", "GND", "C", 55, 55, 36, 36, 90],
[250, "X2_19", 241.1417, 1114.9606, "T", "DIP", "GND", "GND", "C", 55, 55, 36, 36, 90],
[251, "X2_20", 141.1417, 1114.9606, "T", "DIP", "IDE5V", "", "C", 62.992, 62.992, 36, 36, 90],
[252, "X2_20", 141.1417, 1114.9606, "T", "DIP", "IDE5V", "", "C", 62.992, 62.992, 36, 36, 90],
[253, "X2_21", 241.1417, 1214.9606, "T", "DIP", "NET_45", "", "C", 55, 55, 36, 36, 90],
[254, "X2_21", 241.1417, 1214.9606, "T", "DIP", "NET_45", "", "C", 55, 55, 36, 36, 90],
[255, "X2_22", 141.1417, 1214.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[256, "X2_22", 141.1417, 1214.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[257, "X2_23", 241.1417, 1314.9606, "T", "DIP", "IWR_", "", "C", 62.992, 62.992, 36, 36, 90],
[258, "X2_23", 241.1417, 1314.9606, "T", "DIP", "IWR_", "", "C", 62.992, 62.992, 36, 36, 90],
[259, "X2_24", 141.1417, 1314.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[260, "X2_24", 141.1417, 1314.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[261, "X2_25", 241.1417, 1414.9606, "T", "DIP", "IRD_", "", "C", 62.992, 62.992, 36, 36, 90],
[262, "X2_25", 241.1417, 1414.9606, "T", "DIP", "IRD_", "", "C", 62.992, 62.992, 36, 36, 90],
[263, "X2_26", 141.1417, 1414.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[264, "X2_26", 141.1417, 1414.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[265, "X2_27", 241.1417, 1514.9606, "T", "DIP", "ESP_MISO", "", "C", 62.992, 62.992, 36, 36, 90],
[266, "X2_27", 241.1417, 1514.9606, "T", "DIP", "ESP_MISO", "", "C", 62.992, 62.992, 36, 36, 90],
[267, "X2_28", 141.1417, 1514.9606, "T", "DIP", "NET_46", "", "C", 62.992, 62.992, 36, 36, 90],
[268, "X2_28", 141.1417, 1514.9606, "T", "DIP", "NET_46", "", "C", 62.992, 62.992, 36, 36, 90],
[269, "X2_29", 241.1417, 1614.9606, "T", "DIP", "NET_47", "", "C", 62.992, 62.992, 36, 36, 90],
[270, "X2_29", 241.1417, 1614.9606, "T", "DIP", "NET_47", "", "C", 62.992, 62.992, 36, 36, 90],
[271, "X2_30", 141.1417, 1614.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[272, "X2_30", 141.1417, 1614.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[273, "X2_31", 241.1417, 1714.9606, "T", "DIP", "NET_48", "", "C", 62.992, 62.992, 36, 36, 90],
[274, "X2_31", 241.1417, 1714.9606, "T", "DIP", "NET_48", "", "C", 62.992, 62.992, 36, 36, 90],
[275, "X2_32", 141.1417, 1714.9606, "T", "DIP", "NET_49", "", "C", 62.992, 62.992, 36, 36, 90],
[276, "X2_32", 141.1417, 1714.9606, "T", "DIP", "NET_49", "", "C", 62.992, 62.992, 36, 36, 90],
[277, "X2_33", 241.1417, 1814.9606, "T", "DIP", "ESP_MOSI", "", "C", 62.992, 62.992, 36, 36, 90],
[278, "X2_33", 241.1417, 1814.9606, "T", "DIP", "ESP_MOSI", "", "C", 62.992, 62.992, 36, 36, 90],
[279, "X2_34", 141.1417, 1814.9606, "T", "DIP", "NET_50", "", "C", 62.992, 62.992, 36, 36, 90],
[280, "X2_34", 141.1417, 1814.9606, "T", "DIP", "NET_50", "", "C", 62.992, 62.992, 36, 36, 90],
[281, "X2_35", 241.1417, 1914.9606, "T", "DIP", "$1N192", "", "C", 62.992, 62.992, 36, 36, 90],
[282, "X2_35", 241.1417, 1914.9606, "T", "DIP", "$1N192", "", "C", 62.992, 62.992, 36, 36, 90],
[283, "X2_36", 141.1417, 1914.9606, "T", "DIP", "IA2", "", "C", 62.992, 62.992, 36, 36, 90],
[284, "X2_36", 141.1417, 1914.9606, "T", "DIP", "IA2", "", "C", 62.992, 62.992, 36, 36, 90],
[285, "X2_37", 241.1417, 2014.9606, "T", "DIP", "ICS0_", "", "C", 62.992, 62.992, 36, 36, 90],
[286, "X2_37", 241.1417, 2014.9606, "T", "DIP", "ICS0_", "", "C", 62.992, 62.992, 36, 36, 90],
[287, "X2_38", 141.1417, 2014.9606, "T", "DIP", "ICS1_", "", "C", 62.992, 62.992, 36, 36, 90],
[288, "X2_38", 141.1417, 2014.9606, "T", "DIP", "ICS1_", "", "C", 62.992, 62.992, 36, 36, 90],
[289, "X2_39", 241.1417, 2114.9606, "T", "DIP", "DASP_", "", "C", 62.992, 62.992, 36, 36, 90],
[290, "X2_39", 241.1417, 2114.9606, "T", "DIP", "DASP_", "", "C", 62.992, 62.992, 36, 36, 90],
[291, "X2_40", 141.1417, 2114.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[292, "X2_40", 141.1417, 2114.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[293, "X3_1", 1407.6417, 3265.3606, "T", "DIP", "ZXROW0", "", "R", 65, 65, 36, 36, 270],
[294, "X3_1", 1407.6417, 3265.3606, "T", "DIP", "ZXROW0", "", "R", 65, 65, 36, 36, 270],
[295, "X3_2", 1507.6417, 3265.3606, "T", "DIP", "ZXROW1", "", "C", 65, 65, 36, 36, 270],
[296, "X3_2", 1507.6417, 3265.3606, "T", "DIP", "ZXROW1", "", "C", 65, 65, 36, 36, 270],
[297, "X3_3", 1407.6417, 3165.3606, "T", "DIP", "ZXROW2", "", "C", 65, 65, 36, 36, 270],
[298, "X3_3", 1407.6417, 3165.3606, "T", "DIP", "ZXROW2", "", "C", 65, 65, 36, 36, 270],
[299, "X3_4", 1507.6417, 3165.3606, "T", "DIP", "ZXROW3", "", "C", 65, 65, 36, 36, 270],
[300, "X3_4", 1507.6417, 3165.3606, "T", "DIP", "ZXROW3", "", "C", 65, 65, 36, 36, 270],
[301, "X3_5", 1407.6417, 3065.3606, "T", "DIP", "ZXROW4", "", "C", 65, 65, 36, 36, 270],
[302, "X3_5", 1407.6417, 3065.3606, "T", "DIP", "ZXROW4", "", "C", 65, 65, 36, 36, 270],
[303, "X3_6", 1507.6417, 3065.3606, "T", "DIP", "ZXROW5", "", "C", 65, 65, 36, 36, 270],
[304, "X3_6", 1507.6417, 3065.3606, "T", "DIP", "ZXROW5", "", "C", 65, 65, 36, 36, 270],
[305, "X3_7", 1407.6417, 2965.3606, "T", "DIP", "ZXROW6", "", "C", 65, 65, 36, 36, 270],
[306, "X3_7", 1407.6417, 2965.3606, "T", "DIP", "ZXROW6", "", "C", 65, 65, 36, 36, 270],
[307, "X3_8", 1507.6417, 2965.3606, "T", "DIP", "ZXROW7", "", "C", 65, 65, 36, 36, 270],
[308, "X3_8", 1507.6417, 2965.3606, "T", "DIP", "ZXROW7", "", "C", 65, 65, 36, 36, 270],
[309, "X3_9", 1407.6417, 2865.3606, "T", "DIP", "SOFTRST_", "", "C", 65, 65, 36, 36, 270],
[310, "X3_9", 1407.6417, 2865.3606, "T", "DIP", "SOFTRST_", "", "C", 65, 65, 36, 36, 270],
[311, "X3_10", 1507.6417, 2865.3606, "T", "DIP", "ZXCOL4", "", "C", 65, 65, 36, 36, 270],
[312, "X3_10", 1507.6417, 2865.3606, "T", "DIP", "ZXCOL4", "", "C", 65, 65, 36, 36, 270],
[313, "X3_11", 1407.6417, 2765.3606, "T", "DIP", "ZXCOL3", "", "C", 65, 65, 36, 36, 270],
[314, "X3_11", 1407.6417, 2765.3606, "T", "DIP", "ZXCOL3", "", "C", 65, 65, 36, 36, 270],
[315, "X3_12", 1507.6417, 2765.3606, "T", "DIP", "ZXCOL2", "", "C", 65, 65, 36, 36, 270],
[316, "X3_12", 1507.6417, 2765.3606, "T", "DIP", "ZXCOL2", "", "C", 65, 65, 36, 36, 270],
[317, "X3_13", 1407.6417, 2665.3606, "T", "DIP", "ZXCOL1", "", "C", 65, 65, 36, 36, 270],
[318, "X3_13", 1407.6417, 2665.3606, "T", "DIP", "ZXCOL1", "", "C", 65, 65, 36, 36, 270],
[319, "X3_14", 1507.6417, 2665.3606, "T", "DIP", "ZXCOL0", "", "C", 65, 65, 36, 36, 270],
[320, "X3_14", 1507.6417, 2665.3606, "T", "DIP", "ZXCOL0", "", "C", 65, 65, 36, 36, 270],
[321, "X3_15", 1407.6417, 2565.3606, "T", "DIP", "GND", "GND", "C", 65, 65, 36, 36, 270],
[322, "X3_15", 1407.6417, 2565.3606, "T", "DIP", "GND", "GND", "C", 65, 65, 36, 36, 270],
[323, "X3_16", 1507.6417, 2565.3606, "T", "DIP", "JOYL", "", "C", 65, 65, 36, 36, 270],
[324, "X3_16", 1507.6417, 2565.3606, "T", "DIP", "JOYL", "", "C", 65, 65, 36, 36, 270],
[325, "X3_17", 1407.6417, 2465.3606, "T", "DIP", "JOYR", "", "C", 65, 65, 36, 36, 270],
[326, "X3_17", 1407.6417, 2465.3606, "T", "DIP", "JOYR", "", "C", 65, 65, 36, 36, 270],
[327, "X3_18", 1507.6417, 2465.3606, "T", "DIP", "JOYU", "", "C", 65, 65, 36, 36, 270],
[328, "X3_18", 1507.6417, 2465.3606, "T", "DIP", "JOYU", "", "C", 65, 65, 36, 36, 270],
[329, "X3_19", 1407.6417, 2365.3606, "T", "DIP", "JOYD", "", "C", 65, 65, 36, 36, 270],
[330, "X3_19", 1407.6417, 2365.3606, "T", "DIP", "JOYD", "", "C", 65, 65, 36, 36, 270],
[331, "X3_20", 1507.6417, 2365.3606, "T", "DIP", "JOYF", "", "C", 65, 65, 36, 36, 270],
[332, "X3_20", 1507.6417, 2365.3606, "T", "DIP", "JOYF", "", "C", 65, 65, 36, 36, 270],
[333, "X4_1", 1397.6378, 3787.0078, "T", "DIP", "JOYU", "", "R", 60, 60, 36, 36, 90],
[334, "X4_1", 1397.6378, 3787.0078, "T", "DIP", "JOYU", "", "R", 60, 60, 36, 36, 90],
[335, "X4_2", 1297.6378, 3787.0078, "T", "DIP", "JOYF", "", "C", 60, 60, 36, 36, 90],
[336, "X4_2", 1297.6378, 3787.0078, "T", "DIP", "JOYF", "", "C", 60, 60, 36, 36, 90],
[337, "X4_3", 1397.6378, 3887.0078, "T", "DIP", "JOYD", "", "C", 60, 60, 36, 36, 90],
[338, "X4_3", 1397.6378, 3887.0078, "T", "DIP", "JOYD", "", "C", 60, 60, 36, 36, 90],
[339, "X4_4", 1297.6378, 3887.0078, "T", "DIP", "ZXROW2", "", "C", 60, 60, 36, 36, 90],
[340, "X4_4", 1297.6378, 3887.0078, "T", "DIP", "ZXROW2", "", "C", 60, 60, 36, 36, 90],
[341, "X4_5", 1397.6378, 3987.0078, "T", "DIP", "JOYL", "", "C", 60, 60, 36, 36, 90],
[342, "X4_5", 1397.6378, 3987.0078, "T", "DIP", "JOYL", "", "C", 60, 60, 36, 36, 90],
[343, "X4_6", 1297.6378, 3987.0078, "T", "DIP", "GND", "GND", "C", 60, 60, 36, 36, 90],
[344, "X4_6", 1297.6378, 3987.0078, "T", "DIP", "GND", "GND", "C", 60, 60, 36, 36, 90],
[345, "X4_7", 1397.6378, 4087.0078, "T", "DIP", "JOYR", "", "C", 60, 60, 36, 36, 90],
[346, "X4_7", 1397.6378, 4087.0078, "T", "DIP", "JOYR", "", "C", 60, 60, 36, 36, 90],
[347, "X4_8", 1297.6378, 4087.0078, "T", "DIP", "ZXROW5", "", "C", 60, 60, 36, 36, 90],
[348, "X4_8", 1297.6378, 4087.0078, "T", "DIP", "ZXROW5", "", "C", 60, 60, 36, 36, 90],
[349, "X4_9", 1397.6378, 4187.0078, "T", "DIP", "VCC5", "", "C", 60, 60, 36, 36, 90],
[350, "X4_9", 1397.6378, 4187.0078, "T", "DIP", "VCC5", "", "C", 60, 60, 36, 36, 90],
[351, "X4_10", 1297.6378, 4187.0078, "T", "DIP", "NET_51", "", "C", 60, 60, 36, 36, 90],
[352, "X4_10", 1297.6378, 4187.0078, "T", "DIP", "NET_51", "", "C", 60, 60, 36, 36, 90],
[353, "X5_1", 423.4417, 3840.1606, "T", "DIP", "NET_52", "", "R", 65, 65, 36, 36, 0],
[354, "X5_1", 423.4417, 3840.1606, "T", "DIP", "NET_52", "", "R", 65, 65, 36, 36, 0],
[355, "X5_2", 423.4417, 3940.1606, "T", "DIP", "GND", "GND", "C", 65, 65, 36, 36, 0],
[356, "X5_2", 423.4417, 3940.1606, "T", "DIP", "GND", "GND", "C", 65, 65, 36, 36, 0],
[357, "X5_3", 523.4417, 3840.1606, "T", "DIP", "ZIFI_RX", "", "C", 65, 65, 36, 36, 0],
[358, "X5_3", 523.4417, 3840.1606, "T", "DIP", "ZIFI_RX", "", "C", 65, 65, 36, 36, 0],
[359, "X5_4", 523.4417, 3940.1606, "T", "DIP", "NET_53", "", "C", 65, 65, 36, 36, 0],
[360, "X5_4", 523.4417, 3940.1606, "T", "DIP", "NET_53", "", "C", 65, 65, 36, 36, 0],
[361, "X5_5", 623.4417, 3840.1606, "T", "DIP", "NET_54", "", "C", 65, 65, 36, 36, 0],
[362, "X5_5", 623.4417, 3840.1606, "T", "DIP", "NET_54", "", "C", 65, 65, 36, 36, 0],
[363, "X5_6", 623.4417, 3940.1606, "T", "DIP", "NET_55", "", "C", 65, 65, 36, 36, 0],
[364, "X5_6", 623.4417, 3940.1606, "T", "DIP", "NET_55", "", "C", 65, 65, 36, 36, 0],
[365, "X5_7", 723.4417, 3840.1606, "T", "DIP", "NET_56", "", "C", 65, 65, 36, 36, 0],
[366, "X5_7", 723.4417, 3840.1606, "T", "DIP", "NET_56", "", "C", 65, 65, 36, 36, 0],
[367, "X5_8", 723.4417, 3940.1606, "T", "DIP", "NET_57", "", "C", 65, 65, 36, 36, 0],
[368, "X5_8", 723.4417, 3940.1606, "T", "DIP", "NET_57", "", "C", 65, 65, 36, 36, 0],
[369, "X5_9", 823.4417, 3840.1606, "T", "DIP", "ZIFI_TX", "", "C", 65, 65, 36, 36, 0],
[370, "X5_9", 823.4417, 3840.1606, "T", "DIP", "ZIFI_TX", "", "C", 65, 65, 36, 36, 0],
[371, "X5_10", 823.4417, 3940.1606, "T", "DIP", "GND", "GND", "C", 65, 65, 36, 36, 0],
[372, "X5_10", 823.4417, 3940.1606, "T", "DIP", "GND", "GND", "C", 65, 65, 36, 36, 0],
[373, "X6_1", 1791.3386, 3787.0078, "T", "DIP", "ZXCOL3", "", "R", 60, 60, 36, 36, 90],
[374, "X6_1", 1791.3386, 3787.0078, "T", "DIP", "ZXCOL3", "", "R", 60, 60, 36, 36, 90],
[375, "X6_2", 1691.3386, 3787.0078, "T", "DIP", "ZXCOL4", "", "C", 60, 60, 36, 36, 90],
[376, "X6_2", 1691.3386, 3787.0078, "T", "DIP", "ZXCOL4", "", "C", 60, 60, 36, 36, 90],
[377, "X6_3", 1791.3386, 3887.0078, "T", "DIP", "ZXCOL2", "", "C", 60, 60, 36, 36, 90],
[378, "X6_3", 1791.3386, 3887.0078, "T", "DIP", "ZXCOL2", "", "C", 60, 60, 36, 36, 90],
[379, "X6_4", 1691.3386, 3887.0078, "T", "DIP", "ZXROW2", "", "C", 60, 60, 36, 36, 90],
[380, "X6_4", 1691.3386, 3887.0078, "T", "DIP", "ZXROW2", "", "C", 60, 60, 36, 36, 90],
[381, "X6_5", 1791.3386, 3987.0078, "T", "DIP", "ZXCOL1", "", "C", 60, 60, 36, 36, 90],
[382, "X6_5", 1791.3386, 3987.0078, "T", "DIP", "ZXCOL1", "", "C", 60, 60, 36, 36, 90],
[383, "X6_6", 1691.3386, 3987.0078, "T", "DIP", "GND", "GND", "C", 60, 60, 36, 36, 90],
[384, "X6_6", 1691.3386, 3987.0078, "T", "DIP", "GND", "GND", "C", 60, 60, 36, 36, 90],
[385, "X6_7", 1791.3386, 4087.0078, "T", "DIP", "ZXCOL0", "", "C", 60, 60, 36, 36, 90],
[386, "X6_7", 1791.3386, 4087.0078, "T", "DIP", "ZXCOL0", "", "C", 60, 60, 36, 36, 90],
[387, "X6_8", 1691.3386, 4087.0078, "T", "DIP", "ZXROW1", "", "C", 60, 60, 36, 36, 90],
[388, "X6_8", 1691.3386, 4087.0078, "T", "DIP", "ZXROW1", "", "C", 60, 60, 36, 36, 90],
[389, "X6_9", 1791.3386, 4187.0078, "T", "DIP", "VCC5", "", "C", 60, 60, 36, 36, 90],
[390, "X6_9", 1791.3386, 4187.0078, "T", "DIP", "VCC5", "", "C", 60, 60, 36, 36, 90],
[391, "X6_10", 1691.3386, 4187.0078, "T", "DIP", "NET_58", "", "C", 60, 60, 36, 36, 90],
[392, "X6_10", 1691.3386, 4187.0078, "T", "DIP", "NET_58", "", "C", 60, 60, 36, 36, 90],
[393, "X7_1", -908.8583, 4244.9606, "T", "DIP", "L_OUT", "", "R", 65, 65, 36, 36, 0],
[394, "X7_1", -908.8583, 4244.9606, "T", "DIP", "L_OUT", "", "R", 65, 65, 36, 36, 0],
[395, "X7_2", -808.8583, 4244.9606, "T", "DIP", "AGND", "", "C", 65, 65, 36, 36, 0],
[396, "X7_2", -808.8583, 4244.9606, "T", "DIP", "AGND", "", "C", 65, 65, 36, 36, 0],
[397, "X7_3", -708.8583, 4244.9606, "T", "DIP", "AGND", "", "C", 65, 65, 36, 36, 0],
[398, "X7_3", -708.8583, 4244.9606, "T", "DIP", "AGND", "", "C", 65, 65, 36, 36, 0],
[399, "X7_4", -608.8583, 4244.9606, "T", "DIP", "R_OUT", "", "C", 65, 65, 36, 36, 0],
[400, "X7_4", -608.8583, 4244.9606, "T", "DIP", "R_OUT", "", "C", 65, 65, 36, 36, 0],
[401, "PAD1_2", 471.1417, 1414.9606, "T", "DIP", "IDE5V", "", "C", 65, 65, 38, 38, 270],
[402, "PAD1_2", 471.1417, 1414.9606, "T", "DIP", "IDE5V", "", "C", 65, 65, 38, 38, 270],
[403, "PAD2_1", 471.1417, 1514.9606, "T", "DIP", "VCC5", "", "R", 65, 65, 38, 38, 270],
[404, "PAD2_1", 471.1417, 1514.9606, "T", "DIP", "VCC5", "", "R", 65, 65, 38, 38, 270],
[405, "PAD3_2", 189.3717, 3852.4908, "B", "SMD", "$1N45", "", "R", 44.588, 54.213, 0, 0, 180],
[406, "PAD4_1", 268.1117, 3852.4908, "B", "SMD", "ZIFI_RX", "", "R", 44.588, 54.213, 0, 0, 180],
[407, "PAD5_2", 295.1117, 3250.4908, "B", "SMD", "$1N192", "", "R", 44.588, 54.213, 0, 0, 0],
[408, "PAD6_1", 216.3717, 3250.4908, "B", "SMD", "ESP_SCK", "", "R", 44.588, 54.213, 0, 0, 0],
[409, "PAD7_44", 1076.1417, 4949.9606, "T", "DIP", "GND", "GND", "O", 75, 55, 36, 36, 0],
[410, "PAD7_44", 1076.1417, 4949.9606, "T", "DIP", "GND", "GND", "O", 75, 55, 36, 36, 0],
[411, "PAD8_24", 1076.1417, 2949.9606, "T", "DIP", "GND", "GND", "O", 75, 55, 36, 36, 0],
[412, "PAD8_24", 1076.1417, 2949.9606, "T", "DIP", "GND", "GND", "O", 75, 55, 36, 36, 0],
[413, "PAD9_23", 1076.1417, 2849.9606, "T", "DIP", "GND", "GND", "O", 75, 55, 36, 36, 0],
[414, "PAD9_23", 1076.1417, 2849.9606, "T", "DIP", "GND", "GND", "O", 75, 55, 36, 36, 0],
[415, "PAD10_22", 76.1417, 2849.9606, "T", "DIP", "GND", "GND", "O", 75, 55, 36, 36, 0],
[416, "PAD10_22", 76.1417, 2849.9606, "T", "DIP", "GND", "GND", "O", 75, 55, 36, 36, 0],
[417, "PAD11_21", 76.1417, 2949.9606, "T", "DIP", "VCC5", "", "O", 75, 55, 36, 36, 0],
[418, "PAD11_21", 76.1417, 2949.9606, "T", "DIP", "VCC5", "", "O", 75, 55, 36, 36, 0],
[419, "PAD12_19", 76.1417, 3149.9606, "T", "DIP", "ESP_MOSI", "", "O", 75, 55, 36, 36, 0],
[420, "PAD12_19", 76.1417, 3149.9606, "T", "DIP", "ESP_MOSI", "", "O", 75, 55, 36, 36, 0],
[421, "PAD13_18", 76.1417, 3249.9606, "T", "DIP", "ESP_SCK", "", "O", 75, 55, 36, 36, 0],
[422, "PAD13_18", 76.1417, 3249.9606, "T", "DIP", "ESP_SCK", "", "O", 75, 55, 36, 36, 0],
[423, "PAD14_17", 76.1417, 3349.9606, "T", "DIP", "ESP_MISO", "", "O", 75, 55, 36, 36, 0],
[424, "PAD14_17", 76.1417, 3349.9606, "T", "DIP", "ESP_MISO", "", "O", 75, 55, 36, 36, 0],
[425, "PAD15_16", 76.1417, 3449.9606, "T", "DIP", "ESP_CS", "", "O", 75, 55, 36, 36, 0],
[426, "PAD15_16", 76.1417, 3449.9606, "T", "DIP", "ESP_CS", "", "O", 75, 55, 36, 36, 0],
[427, "PAD16_12", 76.1417, 3849.9606, "T", "DIP", "$1N45", "", "O", 75, 55, 36, 36, 0],
[428, "PAD16_12", 76.1417, 3849.9606, "T", "DIP", "$1N45", "", "O", 75, 55, 36, 36, 0],
[429, "PAD17_11", 76.1417, 3949.9606, "T", "DIP", "ZIFI_TX", "", "O", 75, 55, 36, 36, 0],
[430, "PAD17_11", 76.1417, 3949.9606, "T", "DIP", "ZIFI_TX", "", "O", 75, 55, 36, 36, 0],
[431, "PAD18_10", 76.1417, 4049.9606, "T", "DIP", "PCM_DIN", "", "O", 75, 55, 36, 36, 0],
[432, "PAD18_10", 76.1417, 4049.9606, "T", "DIP", "PCM_DIN", "", "O", 75, 55, 36, 36, 0],
[433, "PAD19_9", 76.1417, 4149.9606, "T", "DIP", "PCM_LRCK", "", "O", 75, 55, 36, 36, 0],
[434, "PAD19_9", 76.1417, 4149.9606, "T", "DIP", "PCM_LRCK", "", "O", 75, 55, 36, 36, 0],
[435, "PAD20_8", 76.1417, 4249.9606, "T", "DIP", "PCM_BLK", "", "O", 75, 55, 36, 36, 0],
[436, "PAD20_8", 76.1417, 4249.9606, "T", "DIP", "PCM_BLK", "", "O", 75, 55, 36, 36, 0],
[437, "PAD21_1", -108.8583, 2949.9606, "T", "DIP", "VCC5", "", "C", 65, 65, 36, 36, 90],
[438, "PAD21_1", -108.8583, 2949.9606, "T", "DIP", "VCC5", "", "C", 65, 65, 36, 36, 90],
[439, "PAD22_3", -108.8583, 3149.9606, "T", "DIP", "GND", "GND", "C", 65, 65, 36, 36, 90],
[440, "PAD22_3", -108.8583, 3149.9606, "T", "DIP", "GND", "GND", "C", 65, 65, 36, 36, 90],
[441, "PAD23_4", -108.8583, 3249.9606, "T", "DIP", "PCM_LRCK", "", "C", 65, 65, 36, 36, 90],
[442, "PAD23_4", -108.8583, 3249.9606, "T", "DIP", "PCM_LRCK", "", "C", 65, 65, 36, 36, 90],
[443, "PAD24_5", -108.8583, 3349.9606, "T", "DIP", "PCM_DIN", "", "C", 65, 65, 36, 36, 90],
[444, "PAD24_5", -108.8583, 3349.9606, "T", "DIP", "PCM_DIN", "", "C", 65, 65, 36, 36, 90],
[445, "PAD25_6", -108.8583, 3449.9606, "T", "DIP", "PCM_BLK", "", "C", 65, 65, 36, 36, 90],
[446, "PAD25_6", -108.8583, 3449.9606, "T", "DIP", "PCM_BLK", "", "C", 65, 65, 36, 36, 90],
[447, "PAD26_7", -108.8583, 3549.9606, "T", "DIP", "L_OUT", "", "C", 65, 65, 36, 36, 90],
[448, "PAD26_7", -108.8583, 3549.9606, "T", "DIP", "L_OUT", "", "C", 65, 65, 36, 36, 90],
[449, "PAD27_8", -108.8583, 3649.9606, "T", "DIP", "AGND", "", "C", 65, 65, 36, 36, 90],
[450, "PAD27_8", -108.8583, 3649.9606, "T", "DIP", "AGND", "", "C", 65, 65, 36, 36, 90],
[451, "PAD28_9", -108.8583, 3749.9606, "T", "DIP", "R_OUT", "", "C", 65, 65, 36, 36, 90],
[452, "PAD28_9", -108.8583, 3749.9606, "T", "DIP", "R_OUT", "", "C", 65, 65, 36, 36, 90],
[453, "PAD29_40", 766.1417, 2114.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[454, "PAD29_40", 766.1417, 2114.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[455, "PAD30_39", 866.1417, 2114.9606, "T", "DIP", "DASP_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[456, "PAD30_39", 866.1417, 2114.9606, "T", "DIP", "DASP_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[457, "PAD31_38", 766.1417, 2014.9606, "T", "DIP", "ICS1_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[458, "PAD31_38", 766.1417, 2014.9606, "T", "DIP", "ICS1_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[459, "PAD32_37", 866.1417, 2014.9606, "T", "DIP", "ICS0_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[460, "PAD32_37", 866.1417, 2014.9606, "T", "DIP", "ICS0_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[461, "PAD33_36", 766.1417, 1914.9606, "T", "DIP", "IA2", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[462, "PAD33_36", 766.1417, 1914.9606, "T", "DIP", "IA2", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[463, "PAD34_35", 866.1417, 1914.9606, "T", "DIP", "$1N192", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[464, "PAD34_35", 866.1417, 1914.9606, "T", "DIP", "$1N192", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[465, "PAD35_33", 866.1417, 1814.9606, "T", "DIP", "ESP_MOSI", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[466, "PAD35_33", 866.1417, 1814.9606, "T", "DIP", "ESP_MOSI", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[467, "PAD36_30", 766.1417, 1614.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[468, "PAD36_30", 766.1417, 1614.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[469, "PAD37_27", 866.1417, 1514.9606, "T", "DIP", "ESP_MISO", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[470, "PAD37_27", 866.1417, 1514.9606, "T", "DIP", "ESP_MISO", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[471, "PAD38_26", 766.1417, 1414.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[472, "PAD38_26", 766.1417, 1414.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[473, "PAD39_25", 866.1417, 1414.9606, "T", "DIP", "IRD_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[474, "PAD39_25", 866.1417, 1414.9606, "T", "DIP", "IRD_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[475, "PAD40_24", 766.1417, 1314.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[476, "PAD40_24", 766.1417, 1314.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[477, "PAD41_23", 866.1417, 1314.9606, "T", "DIP", "IWR_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[478, "PAD41_23", 866.1417, 1314.9606, "T", "DIP", "IWR_", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[479, "PAD42_22", 766.1417, 1214.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[480, "PAD42_22", 766.1417, 1214.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[481, "PAD43_20", 766.1417, 1114.9606, "T", "DIP", "$1N233", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[482, "PAD43_20", 766.1417, 1114.9606, "T", "DIP", "$1N233", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[483, "PAD44_19", 866.1417, 1114.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[484, "PAD44_19", 866.1417, 1114.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[485, "PAD45_18", 766.1417, 1014.9606, "T", "DIP", "ID15", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[486, "PAD45_18", 766.1417, 1014.9606, "T", "DIP", "ID15", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[487, "PAD46_17", 866.1417, 1014.9606, "T", "DIP", "ID0", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[488, "PAD46_17", 866.1417, 1014.9606, "T", "DIP", "ID0", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[489, "PAD47_16", 766.1417, 914.9606, "T", "DIP", "ID14", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[490, "PAD47_16", 766.1417, 914.9606, "T", "DIP", "ID14", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[491, "PAD48_15", 866.1417, 914.9606, "T", "DIP", "ID1", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[492, "PAD48_15", 866.1417, 914.9606, "T", "DIP", "ID1", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[493, "PAD49_14", 766.1417, 814.9606, "T", "DIP", "ID13", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[494, "PAD49_14", 766.1417, 814.9606, "T", "DIP", "ID13", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[495, "PAD50_13", 866.1417, 814.9606, "T", "DIP", "ID2", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[496, "PAD50_13", 866.1417, 814.9606, "T", "DIP", "ID2", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[497, "PAD51_12", 766.1417, 714.9606, "T", "DIP", "ID12", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[498, "PAD51_12", 766.1417, 714.9606, "T", "DIP", "ID12", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[499, "PAD52_11", 866.1417, 714.9606, "T", "DIP", "ID3", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[500, "PAD52_11", 866.1417, 714.9606, "T", "DIP", "ID3", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[501, "PAD53_10", 766.1417, 614.9606, "T", "DIP", "ID11", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[502, "PAD53_10", 766.1417, 614.9606, "T", "DIP", "ID11", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[503, "PAD54_9", 866.1417, 614.9606, "T", "DIP", "ID4", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[504, "PAD54_9", 866.1417, 614.9606, "T", "DIP", "ID4", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[505, "PAD55_8", 766.1417, 514.9606, "T", "DIP", "ID10", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[506, "PAD55_8", 766.1417, 514.9606, "T", "DIP", "ID10", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[507, "PAD56_7", 866.1417, 514.9606, "T", "DIP", "ID5", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[508, "PAD56_7", 866.1417, 514.9606, "T", "DIP", "ID5", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[509, "PAD57_6", 766.1417, 414.9606, "T", "DIP", "ID9", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[510, "PAD57_6", 766.1417, 414.9606, "T", "DIP", "ID9", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[511, "PAD58_5", 866.1417, 414.9606, "T", "DIP", "ID6", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[512, "PAD58_5", 866.1417, 414.9606, "T", "DIP", "ID6", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[513, "PAD59_4", 766.1417, 314.9606, "T", "DIP", "ID8", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[514, "PAD59_4", 766.1417, 314.9606, "T", "DIP", "ID8", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[515, "PAD60_3", 866.1417, 314.9606, "T", "DIP", "ID7", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[516, "PAD60_3", 866.1417, 314.9606, "T", "DIP", "ID7", "", "C", 62.992, 62.992, 39.37, 39.37, 90],
[517, "PAD61_2", 766.1417, 214.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[518, "PAD61_2", 766.1417, 214.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 39.37, 39.37, 90],
[519, "PAD62_1", 866.1417, 214.9606, "T", "DIP", "ESP_CS", "", "R", 62.992, 62.992, 39.37, 39.37, 90],
[520, "PAD62_1", 866.1417, 214.9606, "T", "DIP", "ESP_CS", "", "R", 62.992, 62.992, 39.37, 39.37, 90],
[521, "PAD63_1", 241.1417, 214.9606, "T", "DIP", "ESP_CS", "", "R", 62.992, 62.992, 36, 36, 90],
[522, "PAD63_1", 241.1417, 214.9606, "T", "DIP", "ESP_CS", "", "R", 62.992, 62.992, 36, 36, 90],
[523, "PAD64_3", 241.1417, 314.9606, "T", "DIP", "ID7", "", "C", 62.992, 62.992, 36, 36, 90],
[524, "PAD64_3", 241.1417, 314.9606, "T", "DIP", "ID7", "", "C", 62.992, 62.992, 36, 36, 90],
[525, "PAD65_4", 141.1417, 314.9606, "T", "DIP", "ID8", "", "C", 62.992, 62.992, 36, 36, 90],
[526, "PAD65_4", 141.1417, 314.9606, "T", "DIP", "ID8", "", "C", 62.992, 62.992, 36, 36, 90],
[527, "PAD66_5", 241.1417, 414.9606, "T", "DIP", "ID6", "", "C", 62.992, 62.992, 36, 36, 90],
[528, "PAD66_5", 241.1417, 414.9606, "T", "DIP", "ID6", "", "C", 62.992, 62.992, 36, 36, 90],
[529, "PAD67_6", 141.1417, 414.9606, "T", "DIP", "ID9", "", "C", 62.992, 62.992, 36, 36, 90],
[530, "PAD67_6", 141.1417, 414.9606, "T", "DIP", "ID9", "", "C", 62.992, 62.992, 36, 36, 90],
[531, "PAD68_7", 241.1417, 514.9606, "T", "DIP", "ID5", "", "C", 62.992, 62.992, 36, 36, 90],
[532, "PAD68_7", 241.1417, 514.9606, "T", "DIP", "ID5", "", "C", 62.992, 62.992, 36, 36, 90],
[533, "PAD69_8", 141.1417, 514.9606, "T", "DIP", "ID10", "", "C", 62.992, 62.992, 36, 36, 90],
[534, "PAD69_8", 141.1417, 514.9606, "T", "DIP", "ID10", "", "C", 62.992, 62.992, 36, 36, 90],
[535, "PAD70_9", 241.1417, 614.9606, "T", "DIP", "ID4", "", "C", 62.992, 62.992, 36, 36, 90],
[536, "PAD70_9", 241.1417, 614.9606, "T", "DIP", "ID4", "", "C", 62.992, 62.992, 36, 36, 90],
[537, "PAD71_10", 141.1417, 614.9606, "T", "DIP", "ID11", "", "C", 62.992, 62.992, 36, 36, 90],
[538, "PAD71_10", 141.1417, 614.9606, "T", "DIP", "ID11", "", "C", 62.992, 62.992, 36, 36, 90],
[539, "PAD72_11", 241.1417, 714.9606, "T", "DIP", "ID3", "", "C", 62.992, 62.992, 36, 36, 90],
[540, "PAD72_11", 241.1417, 714.9606, "T", "DIP", "ID3", "", "C", 62.992, 62.992, 36, 36, 90],
[541, "PAD73_12", 141.1417, 714.9606, "T", "DIP", "ID12", "", "C", 62.992, 62.992, 36, 36, 90],
[542, "PAD73_12", 141.1417, 714.9606, "T", "DIP", "ID12", "", "C", 62.992, 62.992, 36, 36, 90],
[543, "PAD74_13", 241.1417, 814.9606, "T", "DIP", "ID2", "", "C", 62.992, 62.992, 36, 36, 90],
[544, "PAD74_13", 241.1417, 814.9606, "T", "DIP", "ID2", "", "C", 62.992, 62.992, 36, 36, 90],
[545, "PAD75_14", 141.1417, 814.9606, "T", "DIP", "ID13", "", "C", 62.992, 62.992, 36, 36, 90],
[546, "PAD75_14", 141.1417, 814.9606, "T", "DIP", "ID13", "", "C", 62.992, 62.992, 36, 36, 90],
[547, "PAD76_15", 241.1417, 914.9606, "T", "DIP", "ID1", "", "C", 62.992, 62.992, 36, 36, 90],
[548, "PAD76_15", 241.1417, 914.9606, "T", "DIP", "ID1", "", "C", 62.992, 62.992, 36, 36, 90],
[549, "PAD77_16", 141.1417, 914.9606, "T", "DIP", "ID14", "", "C", 62.992, 62.992, 36, 36, 90],
[550, "PAD77_16", 141.1417, 914.9606, "T", "DIP", "ID14", "", "C", 62.992, 62.992, 36, 36, 90],
[551, "PAD78_17", 241.1417, 1014.9606, "T", "DIP", "ID0", "", "C", 62.992, 62.992, 36, 36, 90],
[552, "PAD78_17", 241.1417, 1014.9606, "T", "DIP", "ID0", "", "C", 62.992, 62.992, 36, 36, 90],
[553, "PAD79_18", 141.1417, 1014.9606, "T", "DIP", "ID15", "", "C", 62.992, 62.992, 36, 36, 90],
[554, "PAD79_18", 141.1417, 1014.9606, "T", "DIP", "ID15", "", "C", 62.992, 62.992, 36, 36, 90],
[555, "PAD80_19", 241.1417, 1114.9606, "T", "DIP", "GND", "GND", "C", 55, 55, 36, 36, 90],
[556, "PAD80_19", 241.1417, 1114.9606, "T", "DIP", "GND", "GND", "C", 55, 55, 36, 36, 90],
[557, "PAD81_20", 141.1417, 1114.9606, "T", "DIP", "IDE5V", "", "C", 62.992, 62.992, 36, 36, 90],
[558, "PAD81_20", 141.1417, 1114.9606, "T", "DIP", "IDE5V", "", "C", 62.992, 62.992, 36, 36, 90],
[559, "PAD82_22", 141.1417, 1214.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[560, "PAD82_22", 141.1417, 1214.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[561, "PAD83_23", 241.1417, 1314.9606, "T", "DIP", "IWR_", "", "C", 62.992, 62.992, 36, 36, 90],
[562, "PAD83_23", 241.1417, 1314.9606, "T", "DIP", "IWR_", "", "C", 62.992, 62.992, 36, 36, 90],
[563, "PAD84_24", 141.1417, 1314.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[564, "PAD84_24", 141.1417, 1314.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[565, "PAD85_25", 241.1417, 1414.9606, "T", "DIP", "IRD_", "", "C", 62.992, 62.992, 36, 36, 90],
[566, "PAD85_25", 241.1417, 1414.9606, "T", "DIP", "IRD_", "", "C", 62.992, 62.992, 36, 36, 90],
[567, "PAD86_26", 141.1417, 1414.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[568, "PAD86_26", 141.1417, 1414.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[569, "PAD87_27", 241.1417, 1514.9606, "T", "DIP", "ESP_MISO", "", "C", 62.992, 62.992, 36, 36, 90],
[570, "PAD87_27", 241.1417, 1514.9606, "T", "DIP", "ESP_MISO", "", "C", 62.992, 62.992, 36, 36, 90],
[571, "PAD88_30", 141.1417, 1614.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[572, "PAD88_30", 141.1417, 1614.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[573, "PAD89_33", 241.1417, 1814.9606, "T", "DIP", "ESP_MOSI", "", "C", 62.992, 62.992, 36, 36, 90],
[574, "PAD89_33", 241.1417, 1814.9606, "T", "DIP", "ESP_MOSI", "", "C", 62.992, 62.992, 36, 36, 90],
[575, "PAD90_35", 241.1417, 1914.9606, "T", "DIP", "$1N192", "", "C", 62.992, 62.992, 36, 36, 90],
[576, "PAD90_35", 241.1417, 1914.9606, "T", "DIP", "$1N192", "", "C", 62.992, 62.992, 36, 36, 90],
[577, "PAD91_36", 141.1417, 1914.9606, "T", "DIP", "IA2", "", "C", 62.992, 62.992, 36, 36, 90],
[578, "PAD91_36", 141.1417, 1914.9606, "T", "DIP", "IA2", "", "C", 62.992, 62.992, 36, 36, 90],
[579, "PAD92_37", 241.1417, 2014.9606, "T", "DIP", "ICS0_", "", "C", 62.992, 62.992, 36, 36, 90],
[580, "PAD92_37", 241.1417, 2014.9606, "T", "DIP", "ICS0_", "", "C", 62.992, 62.992, 36, 36, 90],
[581, "PAD93_38", 141.1417, 2014.9606, "T", "DIP", "ICS1_", "", "C", 62.992, 62.992, 36, 36, 90],
[582, "PAD93_38", 141.1417, 2014.9606, "T", "DIP", "ICS1_", "", "C", 62.992, 62.992, 36, 36, 90],
[583, "PAD94_39", 241.1417, 2114.9606, "T", "DIP", "DASP_", "", "C", 62.992, 62.992, 36, 36, 90],
[584, "PAD94_39", 241.1417, 2114.9606, "T", "DIP", "DASP_", "", "C", 62.992, 62.992, 36, 36, 90],
[585, "PAD95_40", 141.1417, 2114.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[586, "PAD95_40", 141.1417, 2114.9606, "T", "DIP", "GND", "GND", "C", 62.992, 62.992, 36, 36, 90],
[587, "PAD96_20", 1507.6417, 2365.3606, "T", "DIP", "JOYF", "", "C", 65, 65, 36, 36, 270],
[588, "PAD96_20", 1507.6417, 2365.3606, "T", "DIP", "JOYF", "", "C", 65, 65, 36, 36, 270],
[589, "PAD97_19", 1407.6417, 2365.3606, "T", "DIP", "JOYD", "", "C", 65, 65, 36, 36, 270],
[590, "PAD97_19", 1407.6417, 2365.3606, "T", "DIP", "JOYD", "", "C", 65, 65, 36, 36, 270],
[591, "PAD98_18", 1507.6417, 2465.3606, "T", "DIP", "JOYU", "", "C", 65, 65, 36, 36, 270],
[592, "PAD98_18", 1507.6417, 2465.3606, "T", "DIP", "JOYU", "", "C", 65, 65, 36, 36, 270],
[593, "PAD99_17", 1407.6417, 2465.3606, "T", "DIP", "JOYR", "", "C", 65, 65, 36, 36, 270],
[594, "PAD99_17", 1407.6417, 2465.3606, "T", "DIP", "JOYR", "", "C", 65, 65, 36, 36, 270],
[595, "PAD100_16", 1507.6417, 2565.3606, "T", "DIP", "JOYL", "", "C", 65, 65, 36, 36, 270],
[596, "PAD100_16", 1507.6417, 2565.3606, "T", "DIP", "JOYL", "", "C", 65, 65, 36, 36, 270],
[597, "PAD101_15", 1407.6417, 2565.3606, "T", "DIP", "GND", "GND", "C", 65, 65, 36, 36, 270],
[598, "PAD101_15", 1407.6417, 2565.3606, "T", "DIP", "GND", "GND", "C", 65, 65, 36, 36, 270],
[599, "PAD102_14", 1507.6417, 2665.3606, "T", "DIP", "ZXCOL0", "", "C", 65, 65, 36, 36, 270],
[600, "PAD102_14", 1507.6417, 2665.3606, "T", "DIP", "ZXCOL0", "", "C", 65, 65, 36, 36, 270],
[601, "PAD103_13", 1407.6417, 2665.3606, "T", "DIP", "ZXCOL1", "", "C", 65, 65, 36, 36, 270],
[602, "PAD103_13", 1407.6417, 2665.3606, "T", "DIP", "ZXCOL1", "", "C", 65, 65, 36, 36, 270],
[603, "PAD104_12", 1507.6417, 2765.3606, "T", "DIP", "ZXCOL2", "", "C", 65, 65, 36, 36, 270],
[604, "PAD104_12", 1507.6417, 2765.3606, "T", "DIP", "ZXCOL2", "", "C", 65, 65, 36, 36, 270],
[605, "PAD105_11", 1407.6417, 2765.3606, "T", "DIP", "ZXCOL3", "", "C", 65, 65, 36, 36, 270],
[606, "PAD105_11", 1407.6417, 2765.3606, "T", "DIP", "ZXCOL3", "", "C", 65, 65, 36, 36, 270],
[607, "PAD106_10", 1507.6417, 2865.3606, "T", "DIP", "ZXCOL4", "", "C", 65, 65, 36, 36, 270],
[608, "PAD106_10", 1507.6417, 2865.3606, "T", "DIP", "ZXCOL4", "", "C", 65, 65, 36, 36, 270],
[609, "PAD107_9", 1407.6417, 2865.3606, "T", "DIP", "SOFTRST_", "", "C", 65, 65, 36, 36, 270],
[610, "PAD107_9", 1407.6417, 2865.3606, "T", "DIP", "SOFTRST_", "", "C", 65, 65, 36, 36, 270],
[611, "PAD108_8", 1507.6417, 2965.3606, "T", "DIP", "ZXROW7", "", "C", 65, 65, 36, 36, 270],
[612, "PAD108_8", 1507.6417, 2965.3606, "T", "DIP", "ZXROW7", "", "C", 65, 65, 36, 36, 270],
[613, "PAD109_7", 1407.6417, 2965.3606, "T", "DIP", "ZXROW6", "", "C", 65, 65, 36, 36, 270],
[614, "PAD109_7", 1407.6417, 2965.3606, "T", "DIP", "ZXROW6", "", "C", 65, 65, 36, 36, 270],
[615, "PAD110_6", 1507.6417, 3065.3606, "T", "DIP", "ZXROW5", "", "C", 65, 65, 36, 36, 270],
[616, "PAD110_6", 1507.6417, 3065.3606, "T", "DIP", "ZXROW5", "", "C", 65, 65, 36, 36, 270],
[617, "PAD111_5", 1407.6417, 3065.3606, "T", "DIP", "ZXROW4", "", "C", 65, 65, 36, 36, 270],
[618, "PAD111_5", 1407.6417, 3065.3606, "T", "DIP", "ZXROW4", "", "C", 65, 65, 36, 36, 270],
[619, "PAD112_4", 1507.6417, 3165.3606, "T", "DIP", "ZXROW3", "", "C", 65, 65, 36, 36, 270],
[620, "PAD112_4", 1507.6417, 3165.3606, "T", "DIP", "ZXROW3", "", "C", 65, 65, 36, 36, 270],
[621, "PAD113_3", 1407.6417, 3165.3606, "T", "DIP", "ZXROW2", "", "C", 65, 65, 36, 36, 270],
[622, "PAD113_3", 1407.6417, 3165.3606, "T", "DIP", "ZXROW2", "", "C", 65, 65, 36, 36, 270],
[623, "PAD114_2", 1507.6417, 3265.3606, "T", "DIP", "ZXROW1", "", "C", 65, 65, 36, 36, 270],
[624, "PAD114_2", 1507.6417, 3265.3606, "T", "DIP", "ZXROW1", "", "C", 65, 65, 36, 36, 270],
[625, "PAD115_1", 1407.6417, 3265.3606, "T", "DIP", "ZXROW0", "", "R", 65, 65, 36, 36, 270],
[626, "PAD115_1", 1407.6417, 3265.3606, "T", "DIP", "ZXROW0", "", "R", 65, 65, 36, 36, 270],
[627, "PAD116_1", 1397.6378, 3787.0078, "T", "DIP", "JOYU", "", "R", 60, 60, 36, 36, 90],
[628, "PAD116_1", 1397.6378, 3787.0078, "T", "DIP", "JOYU", "", "R", 60, 60, 36, 36, 90],
[629, "PAD117_2", 1297.6378, 3787.0078, "T", "DIP", "JOYF", "", "C", 60, 60, 36, 36, 90],
[630, "PAD117_2", 1297.6378, 3787.0078, "T", "DIP", "JOYF", "", "C", 60, 60, 36, 36, 90],
[631, "PAD118_3", 1397.6378, 3887.0078, "T", "DIP", "JOYD", "", "C", 60, 60, 36, 36, 90],
[632, "PAD118_3", 1397.6378, 3887.0078, "T", "DIP", "JOYD", "", "C", 60, 60, 36, 36, 90],
[633, "PAD119_4", 1297.6378, 3887.0078, "T", "DIP", "ZXROW2", "", "C", 60, 60, 36, 36, 90],
[634, "PAD119_4", 1297.6378, 3887.0078, "T", "DIP", "ZXROW2", "", "C", 60, 60, 36, 36, 90],
[635, "PAD120_5", 1397.6378, 3987.0078, "T", "DIP", "JOYL", "", "C", 60, 60, 36, 36, 90],
[636, "PAD120_5", 1397.6378, 3987.0078, "T", "DIP", "JOYL", "", "C", 60, 60, 36, 36, 90],
[637, "PAD121_6", 1297.6378, 3987.0078, "T", "DIP", "GND", "GND", "C", 60, 60, 36, 36, 90],
[638, "PAD121_6", 1297.6378, 3987.0078, "T", "DIP", "GND", "GND", "C", 60, 60, 36, 36, 90],
[639, "PAD122_7", 1397.6378, 4087.0078, "T", "DIP", "JOYR", "", "C", 60, 60, 36, 36, 90],
[640, "PAD122_7", 1397.6378, 4087.0078, "T", "DIP", "JOYR", "", "C", 60, 60, 36, 36, 90],
[641, "PAD123_8", 1297.6378, 4087.0078, "T", "DIP", "ZXROW5", "", "C", 60, 60, 36, 36, 90],
[642, "PAD123_8", 1297.6378, 4087.0078, "T", "DIP", "ZXROW5", "", "C", 60, 60, 36, 36, 90],
[643, "PAD124_9", 1397.6378, 4187.0078, "T", "DIP", "VCC5", "", "C", 60, 60, 36, 36, 90],
[644, "PAD124_9", 1397.6378, 4187.0078, "T", "DIP", "VCC5", "", "C", 60, 60, 36, 36, 90],
[645, "PAD125_10", 823.4417, 3940.1606, "T", "DIP", "GND", "GND", "C", 65, 65, 36, 36, 0],
[646, "PAD125_10", 823.4417, 3940.1606, "T", "DIP", "GND", "GND", "C", 65, 65, 36, 36, 0],
[647, "PAD126_9", 823.4417, 3840.1606, "T", "DIP", "ZIFI_TX", "", "C", 65, 65, 36, 36, 0],
[648, "PAD126_9", 823.4417, 3840.1606, "T", "DIP", "ZIFI_TX", "", "C", 65, 65, 36, 36, 0],
[649, "PAD127_3", 523.4417, 3840.1606, "T", "DIP", "ZIFI_RX", "", "C", 65, 65, 36, 36, 0],
[650, "PAD127_3", 523.4417, 3840.1606, "T", "DIP", "ZIFI_RX", "", "C", 65, 65, 36, 36, 0],
[651, "PAD128_2", 423.4417, 3940.1606, "T", "DIP", "GND", "GND", "C", 65, 65, 36, 36, 0],
[652, "PAD128_2", 423.4417, 3940.1606, "T", "DIP", "GND", "GND", "C", 65, 65, 36, 36, 0],
[653, "PAD129_1", 1791.3386, 3787.0078, "T", "DIP", "ZXCOL3", "", "R", 60, 60, 36, 36, 90],
[654, "PAD129_1", 1791.3386, 3787.0078, "T", "DIP", "ZXCOL3", "", "R", 60, 60, 36, 36, 90],
[655, "PAD130_2", 1691.3386, 3787.0078, "T", "DIP", "ZXCOL4", "", "C", 60, 60, 36, 36, 90],
[656, "PAD130_2", 1691.3386, 3787.0078, "T", "DIP", "ZXCOL4", "", "C", 60, 60, 36, 36, 90],
[657, "PAD131_3", 1791.3386, 3887.0078, "T", "DIP", "ZXCOL2", "", "C", 60, 60, 36, 36, 90],
[658, "PAD131_3", 1791.3386, 3887.0078, "T", "DIP", "ZXCOL2", "", "C", 60, 60, 36, 36, 90],
[659, "PAD132_4", 1691.3386, 3887.0078, "T", "DIP", "ZXROW2", "", "C", 60, 60, 36, 36, 90],
[660, "PAD132_4", 1691.3386, 3887.0078, "T", "DIP", "ZXROW2", "", "C", 60, 60, 36, 36, 90],
[661, "PAD133_5", 1791.3386, 3987.0078, "T", "DIP", "ZXCOL1", "", "C", 60, 60, 36, 36, 90],
[662, "PAD133_5", 1791.3386, 3987.0078, "T", "DIP", "ZXCOL1", "", "C", 60, 60, 36, 36, 90],
[663, "PAD134_6", 1691.3386, 3987.0078, "T", "DIP", "GND", "GND", "C", 60, 60, 36, 36, 90],
[664, "PAD134_6", 1691.3386, 3987.0078, "T", "DIP", "GND", "GND", "C", 60, 60, 36, 36, 90],
[665, "PAD135_7", 1791.3386, 4087.0078, "T", "DIP", "ZXCOL0", "", "C", 60, 60, 36, 36, 90],
[666, "PAD135_7", 1791.3386, 4087.0078, "T", "DIP", "ZXCOL0", "", "C", 60, 60, 36, 36, 90],
[667, "PAD136_8", 1691.3386, 4087.0078, "T", "DIP", "ZXROW1", "", "C", 60, 60, 36, 36, 90],
[668, "PAD136_8", 1691.3386, 4087.0078, "T", "DIP", "ZXROW1", "", "C", 60, 60, 36, 36, 90],
[669, "PAD137_9", 1791.3386, 4187.0078, "T", "DIP", "VCC5", "", "C", 60, 60, 36, 36, 90],
[670, "PAD137_9", 1791.3386, 4187.0078, "T", "DIP", "VCC5", "", "C", 60, 60, 36, 36, 90],
[671, "PAD138_1", -908.8583, 4244.9606, "T", "DIP", "L_OUT", "", "R", 65, 65, 36, 36, 0],
[672, "PAD138_1", -908.8583, 4244.9606, "T", "DIP", "L_OUT", "", "R", 65, 65, 36, 36, 0],
[673, "PAD139_2", -808.8583, 4244.9606, "T", "DIP", "AGND", "", "C", 65, 65, 36, 36, 0],
[674, "PAD139_2", -808.8583, 4244.9606, "T", "DIP", "AGND", "", "C", 65, 65, 36, 36, 0],
[675, "PAD140_3", -708.8583, 4244.9606, "T", "DIP", "AGND", "", "C", 65, 65, 36, 36, 0],
[676, "PAD140_3", -708.8583, 4244.9606, "T", "DIP", "AGND", "", "C", 65, 65, 36, 36, 0],
[677, "PAD141_4", -608.8583, 4244.9606, "T", "DIP", "R_OUT", "", "C", 65, 65, 36, 36, 0],
[678, "PAD141_4", -608.8583, 4244.9606, "T", "DIP", "R_OUT", "", "C", 65, 65, 36, 36, 0],
[679, "PAD142_1", 623.7817, 2454.9606, "T", "SMD", "VCC5", "", "R", 74.803, 93.543, 0, 0, 180],
[680, "PAD143_2", 828.5017, 2454.9606, "T", "SMD", "GND", "GND", "R", 74.803, 93.543, 0, 0, 180],
[681, "PAD144_1", 621.1417, 1657.5206, "T", "DIP", "VCC5", "", "C", 70.866, 70.866, 39.37, 39.37, 270],
[682, "PAD144_1", 621.1417, 1657.5206, "T", "DIP", "VCC5", "", "C", 70.866, 70.866, 39.37, 39.37, 270],
[683, "PAD145_2", 621.1417, 1315.0006, "T", "DIP", "$1N233", "", "C", 70.866, 70.866, 39.37, 39.37, 270],
[684, "PAD145_2", 621.1417, 1315.0006, "T", "DIP", "$1N233", "", "C", 70.866, 70.866, 39.37, 39.37, 270]
]
}
}
@@ -0,0 +1,62 @@
G04 Layer: BoardOutlineLayer*
G04 EasyEDA Pro v3.2.148, 2026-08-18 14:30:59*
G04 Gerber Generator version 0.3*
G04 Scale: 100 percent, Rotated: No, Reflected: No*
G04 Dimensions in millimeters*
G04 Leading zeros omitted, absolute positions, 4 integers and 5 decimals*
G04 Generated by one-click*
G04 Object Configs: Pad,Via,Track,Text,Image,Dimension,BoardOutline,BoardCutout,CopperFilled,SolidRegion,FPCStiffener,Line,PlaneZone,ComponentProperty,ComponentSilkscreen,TearDrop*
G04 Layer Configs: 1,2,3,4,5,6,7,8,9,10,11,13,14,56*
G04 Copper Areas: 0*
%FSLAX45Y45*%
%MOMM*%
%ADD10C,0.254*%
%ADD11C,0.8282*%
G75*
G04 PolygonModel Start*
G54D10*
G01X4777706Y5500016D02*
G01X2454000Y5500016D01*
G01X2454000Y5500016D02*
G01X2454000Y150000D01*
G01X4927706Y5650016D02*
G01X4900000Y11150000D01*
G01X2750004Y13499997D02*
G01X150000Y13500000D01*
G01X0Y13350000D02*
G01X0Y10946000D01*
G01X2304000Y0D02*
G01X150000Y0D01*
G01X-2572300Y10796000D02*
G01X-2572300Y6796000D01*
G01X-2422300Y10946000D02*
G01X0Y10946000D01*
G01X-2422300Y6646000D02*
G01X0Y6646000D01*
G01X2900000Y11300000D02*
G01X2900004Y13349997D01*
G01X2900000Y11300000D02*
G01X4750000Y11300000D01*
G01X0Y6646000D02*
G01X0Y150000D01*
G01X2900004Y13349997D02*
G03X2750004Y13499997I-150000J0D01*
G01X150000Y13500000D02*
G03X0Y13350000I0J-150000D01*
G01X4777706Y5500016D02*
G03X4927706Y5650016I0J150000D01*
G01X4900000Y11150000D02*
G03X4750000Y11300000I-150000J0D01*
G01X-2572300Y6796000D02*
G03X-2422300Y6646000I150000J0D01*
G01X-2422300Y10946000D02*
G03X-2572300Y10796000I0J-150000D01*
G01X2304000Y0D02*
G03X2454000Y150000I0J150000D01*
G01X0Y150000D02*
G03X150000Y0I150000J0D01*
M02*
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,85 @@
G04 Layer: BottomPasteMaskLayer*
G04 EasyEDA Pro v3.2.148, 2026-08-18 14:30:59*
G04 Gerber Generator version 0.3*
G04 Scale: 100 percent, Rotated: No, Reflected: No*
G04 Dimensions in millimeters*
G04 Leading zeros omitted, absolute positions, 4 integers and 5 decimals*
G04 Generated by one-click*
G04 Object Configs: Pad,Via,Track,Text,Image,Dimension,BoardOutline,BoardCutout,CopperFilled,SolidRegion,FPCStiffener,Line,PlaneZone,ComponentProperty,ComponentSilkscreen,TearDrop*
G04 Layer Configs: 1,2,3,4,5,6,7,8,9,10,11,13,14,56*
G04 Copper Areas: 0*
%FSLAX45Y45*%
%MOMM*%
%ADD10C,0.254*%
%ADD11C,0.8675*%
G75*
G04 PolygonModel Start*
G36*
G01X751006Y9721326D02*
G01X746005Y9716325D01*
G01X631004Y9716325D01*
G01X626005Y9721326D01*
G01X626503Y9763323D01*
G01X670503Y9763323D01*
G01X670503Y9808321D01*
G01X625502Y9808321D01*
G01X626005Y9849324D01*
G01X631004Y9854326D01*
G01X746005Y9854326D01*
G01X751006Y9849324D01*
G01X751006Y9721326D01*
G37*
G36*
G01X536005Y9721326D02*
G01X531004Y9716325D01*
G01X416006Y9716325D01*
G01X411004Y9721326D01*
G01X411004Y9849324D01*
G01X416006Y9854326D01*
G01X531004Y9854326D01*
G01X536005Y9849324D01*
G01X535502Y9808321D01*
G01X491504Y9808321D01*
G01X491504Y9763323D01*
G01X536003Y9763323D01*
G01X536005Y9721326D01*
G37*
G36*
G01X479582Y8320247D02*
G01X484583Y8325248D01*
G01X599584Y8325248D01*
G01X604583Y8320247D01*
G01X604085Y8278251D01*
G01X560084Y8278251D01*
G01X560084Y8233252D01*
G01X605086Y8233252D01*
G01X604583Y8192249D01*
G01X599584Y8187247D01*
G01X484583Y8187247D01*
G01X479582Y8192249D01*
G01X479582Y8320247D01*
G37*
G36*
G01X694583Y8320247D02*
G01X699584Y8325248D01*
G01X814582Y8325248D01*
G01X819584Y8320247D01*
G01X819584Y8192249D01*
G01X814582Y8187247D01*
G01X699584Y8187247D01*
G01X694583Y8192249D01*
G01X695085Y8233252D01*
G01X739083Y8233252D01*
G01X739083Y8278251D01*
G01X694585Y8278251D01*
G01X694583Y8320247D01*
G37*
G04 Pad Start*
G04 Pad End*
G54D10*
M02*
@@ -0,0 +1,184 @@
G04 Layer: BottomSilkscreenLayer*
G04 EasyEDA Pro v3.2.148, 2026-08-18 14:30:59*
G04 Gerber Generator version 0.3*
G04 Scale: 100 percent, Rotated: No, Reflected: No*
G04 Dimensions in millimeters*
G04 Leading zeros omitted, absolute positions, 4 integers and 5 decimals*
G04 Generated by one-click*
G04 Object Configs: Pad,Via,Track,Text,Image,Dimension,BoardOutline,BoardCutout,CopperFilled,SolidRegion,FPCStiffener,Line,PlaneZone,ComponentProperty,ComponentSilkscreen,TearDrop*
G04 Layer Configs: 1,2,3,4,5,6,7,8,9,10,11,13,14,56*
G04 Copper Areas: 0*
%FSLAX45Y45*%
%MOMM*%
%ADD10C,0.1524*%
%ADD11C,0.254*%
%ADD12C,0.6821*%
G75*
G04 Text Start*
G54D10*
G01X616945Y9961095D02*
G01X616945Y9911311D01*
G01X616945Y9961095D02*
G01X595609Y9961095D01*
G01X588243Y9958809D01*
G01X585957Y9956523D01*
G01X583671Y9951697D01*
G01X583671Y9946871D01*
G01X585957Y9942045D01*
G01X588243Y9939759D01*
G01X595609Y9937473D01*
G01X616945Y9937473D01*
G01X600181Y9937473D02*
G01X583671Y9911311D01*
G01X557001Y9951697D02*
G01X552175Y9953983D01*
G01X545063Y9961095D01*
G01X545063Y9911311D01*
G01X650600Y9649437D02*
G01X645774Y9651723D01*
G01X638662Y9658835D01*
G01X638662Y9609051D01*
G01X611992Y9658835D02*
G01X611992Y9609051D01*
G01X588116Y9642325D02*
G01X611992Y9618449D01*
G01X602340Y9628101D02*
G01X585830Y9609051D01*
G01X559160Y9609051D02*
G01X542396Y9609051D01*
G01X547222Y9616163D01*
G01X552048Y9623275D01*
G01X554334Y9630387D01*
G01X554334Y9637499D01*
G01X552048Y9644611D01*
G01X549508Y9649437D01*
G01X544936Y9654263D01*
G01X540110Y9656549D01*
G01X530458Y9656549D01*
G01X525886Y9654263D01*
G01X521060Y9649437D01*
G01X518774Y9644611D01*
G01X516234Y9637499D01*
G01X516234Y9630387D01*
G01X518774Y9623275D01*
G01X523346Y9616163D01*
G01X528172Y9609051D01*
G01X511408Y9609051D01*
G01X691113Y8442175D02*
G01X691113Y8392391D01*
G01X691113Y8442175D02*
G01X669777Y8442175D01*
G01X662411Y8439889D01*
G01X660125Y8437603D01*
G01X657839Y8432777D01*
G01X657839Y8427951D01*
G01X660125Y8423125D01*
G01X662411Y8420839D01*
G01X669777Y8418553D01*
G01X691113Y8418553D01*
G01X674349Y8418553D02*
G01X657839Y8392391D01*
G01X628883Y8430491D02*
G01X628883Y8432777D01*
G01X626343Y8437603D01*
G01X624057Y8439889D01*
G01X619231Y8442175D01*
G01X609833Y8442175D01*
G01X605007Y8439889D01*
G01X602467Y8437603D01*
G01X600181Y8432777D01*
G01X600181Y8427951D01*
G01X602467Y8423125D01*
G01X607293Y8416013D01*
G01X631169Y8392391D01*
G01X597895Y8392391D01*
G01X714100Y8115277D02*
G01X709274Y8117563D01*
G01X702162Y8124675D01*
G01X702162Y8074891D01*
G01X675492Y8124675D02*
G01X675492Y8074891D01*
G01X651616Y8108165D02*
G01X675492Y8084289D01*
G01X665840Y8093941D02*
G01X649330Y8074891D01*
G01X622660Y8074891D02*
G01X605896Y8074891D01*
G01X610722Y8082003D01*
G01X615548Y8089115D01*
G01X617834Y8096227D01*
G01X617834Y8103339D01*
G01X615548Y8110451D01*
G01X613008Y8115277D01*
G01X608436Y8120103D01*
G01X603610Y8122389D01*
G01X593958Y8122389D01*
G01X589386Y8120103D01*
G01X584560Y8115277D01*
G01X582274Y8110451D01*
G01X579734Y8103339D01*
G01X579734Y8096227D01*
G01X582274Y8089115D01*
G01X586846Y8082003D01*
G01X591672Y8074891D01*
G01X574908Y8074891D01*
G04 Text End*
G04 PolygonModel Start*
G01X533384Y9693617D02*
G01X404890Y9693617D01*
G01X404890Y9693617D02*
G01X404890Y9877036D01*
G01X404890Y9877036D02*
G01X533384Y9877036D01*
G01X628624Y9693617D02*
G01X757117Y9693617D01*
G01X757117Y9693617D02*
G01X757117Y9877036D01*
G01X757117Y9877036D02*
G01X628624Y9877036D01*
G01X697204Y8347956D02*
G01X825697Y8347956D01*
G01X825697Y8347956D02*
G01X825697Y8164537D01*
G01X825697Y8164537D02*
G01X697204Y8164537D01*
G01X601964Y8347956D02*
G01X473470Y8347956D01*
G01X473470Y8347956D02*
G01X473470Y8164537D01*
G01X473470Y8164537D02*
G01X601964Y8164537D01*
G54D11*
G01X2327000Y406300D02*
G01X2327000Y5511700D01*
G01X1819000Y406300D02*
G01X1819000Y5511700D01*
G01X1819000Y5511700D02*
G01X2327000Y5511700D01*
G01X1819000Y406300D02*
G01X2327000Y406300D01*
G01X3702410Y8433716D02*
G01X3702410Y8167016D01*
G01X3702410Y8167016D02*
G01X3461110Y8167016D01*
G01X3952397Y8441006D02*
G01X3952397Y5861001D01*
G01X3452398Y5861001D01*
G01X3452398Y8441006D01*
G01X3952397Y8441006D01*
G01X923142Y10135008D02*
G01X2243942Y10135008D01*
G01X2243942Y9627008D01*
G01X923142Y9627008D01*
G01X923142Y10135008D01*
G01X923142Y9881008D02*
G01X1202542Y9881008D01*
G01X1202542Y9627008D01*
G01X923142Y9627008D01*
G01X923142Y9881008D01*
M02*
@@ -0,0 +1,260 @@
G04 Layer: BottomSolderMaskLayer*
G04 EasyEDA Pro v3.2.148, 2026-08-18 14:30:59*
G04 Gerber Generator version 0.3*
G04 Scale: 100 percent, Rotated: No, Reflected: No*
G04 Dimensions in millimeters*
G04 Leading zeros omitted, absolute positions, 4 integers and 5 decimals*
G04 Generated by one-click*
G04 Object Configs: Pad,Via,Track,Text,Image,Dimension,BoardOutline,BoardCutout,CopperFilled,SolidRegion,FPCStiffener,Line,PlaneZone,ComponentProperty,ComponentSilkscreen,TearDrop*
G04 Layer Configs: 1,2,3,4,5,6,7,8,9,10,11,13,14,56*
G04 Copper Areas: 0*
%FSLAX45Y45*%
%MOMM*%
%AMRoundRect*1,1,$1,$2,$3*1,1,$1,$4,$5*1,1,$1,0-$2,0-$3*1,1,$1,0-$4,0-$5*20,1,$1,$2,$3,$4,$5,0*20,1,$1,$4,$5,0-$2,0-$3,0*20,1,$1,0-$2,0-$3,0-$4,0-$5,0*20,1,$1,0-$4,0-$5,$2,$3,0*4,1,4,$2,$3,$4,$5,0-$2,0-$3,0-$4,0-$5,$2,$3,0*%
%ADD10C,1.7526*%
%ADD11RoundRect,0.1016X-0.8255X-0.8255X-0.8255X0.8255*%
%ADD12RoundRect,0.1016X-0.56627X0.68851X0.56627X0.68851*%
%ADD13O,2.0066X1.4986*%
%ADD14RoundRect,0.1016X-0.9525X0.6985X0.9525X0.6985*%
%ADD15C,1.8585*%
%ADD16C,1.8542*%
%ADD17C,1.7016*%
%ADD18RoundRect,0.1016X-0.8X-0.8X-0.8X0.8*%
%ADD19C,1.4986*%
%ADD20RoundRect,0.1016X-0.762X-0.762X-0.762X0.762*%
%ADD21C,1.6256*%
%ADD22RoundRect,0.1016X-0.8255X0.8255X0.8255X0.8255*%
%ADD23C,1.9016*%
G75*
G04 Pad Start*
G54D10*
G01X1196700Y3594000D03*
G54D11*
G01X1196700Y3848000D03*
G54D12*
G01X481004Y9785327D03*
G01X681004Y9785327D03*
G01X749584Y8256247D03*
G01X549584Y8256247D03*
G54D13*
G01X2733400Y12572900D03*
G01X2733400Y12318900D03*
G01X2733400Y12064900D03*
G01X2733400Y11810900D03*
G01X2733400Y11556900D03*
G01X2733400Y11302900D03*
G01X2733400Y11048900D03*
G01X2733400Y10794900D03*
G01X2733400Y10540900D03*
G01X2733400Y10286900D03*
G01X2733400Y10032900D03*
G01X2733400Y9778900D03*
G01X2733400Y9524900D03*
G01X2733400Y9270900D03*
G01X2733400Y9016900D03*
G01X2733400Y8762900D03*
G01X2733400Y8508900D03*
G01X2733400Y8254900D03*
G01X2733400Y8000900D03*
G01X2733400Y7746900D03*
G01X2733400Y7492900D03*
G01X2733400Y7238900D03*
G01X193400Y7238900D03*
G01X193400Y7492900D03*
G01X193400Y7746900D03*
G01X193400Y8000900D03*
G01X193400Y8254900D03*
G01X193400Y8508900D03*
G01X193400Y8762900D03*
G01X193400Y9016900D03*
G01X193400Y9270900D03*
G01X193400Y9524900D03*
G01X193400Y9778900D03*
G01X193400Y10032900D03*
G01X193400Y10286900D03*
G01X193400Y10540900D03*
G01X193400Y10794900D03*
G01X193400Y11048900D03*
G01X193400Y11302900D03*
G01X193400Y11556900D03*
G01X193400Y11810900D03*
G01X193400Y12064900D03*
G01X193400Y12318900D03*
G54D14*
G01X193400Y12572900D03*
G54D16*
G01X-276500Y7492900D03*
G01X-276500Y7746900D03*
G01X-276500Y8000900D03*
G01X-276500Y8254900D03*
G01X-276500Y8508900D03*
G01X-276500Y8762900D03*
G01X-276500Y9016900D03*
G01X-276500Y9270900D03*
G01X-276500Y9524900D03*
G01X-2308500Y9016900D03*
G01X-2308500Y8762900D03*
G01X-2308500Y8508900D03*
G01X-2308500Y8254900D03*
G01X-2308500Y8000900D03*
G01X-2308500Y7746900D03*
G54D17*
G01X1946000Y5372000D03*
G01X2200000Y5372000D03*
G01X1946000Y5118000D03*
G01X2200000Y5118000D03*
G01X1946000Y4864000D03*
G01X2200000Y4864000D03*
G01X1946000Y4610000D03*
G01X2200000Y4610000D03*
G01X1946000Y4356000D03*
G01X2200000Y4356000D03*
G01X1946000Y4102000D03*
G01X2200000Y4102000D03*
G01X1946000Y3848000D03*
G01X2200000Y3848000D03*
G01X1946000Y3594000D03*
G01X2200000Y3594000D03*
G01X1946000Y3340000D03*
G01X2200000Y3340000D03*
G01X1946000Y3086000D03*
G01X2200000Y3086000D03*
G01X1946000Y2832000D03*
G01X2200000Y2832000D03*
G01X1946000Y2578000D03*
G01X2200000Y2578000D03*
G01X1946000Y2324000D03*
G01X2200000Y2324000D03*
G01X1946000Y2070000D03*
G01X2200000Y2070000D03*
G01X1946000Y1816000D03*
G01X2200000Y1816000D03*
G01X1946000Y1562000D03*
G01X2200000Y1562000D03*
G01X1946000Y1308000D03*
G01X2200000Y1308000D03*
G01X1946000Y1054000D03*
G01X2200000Y1054000D03*
G01X1946000Y800000D03*
G01X2200000Y800000D03*
G01X1946000Y546000D03*
G54D18*
G01X2200000Y546000D03*
G01X612500Y546000D03*
G54D17*
G01X358500Y546000D03*
G01X612500Y800000D03*
G01X358500Y800000D03*
G01X612500Y1054000D03*
G01X358500Y1054000D03*
G01X612500Y1308000D03*
G01X358500Y1308000D03*
G01X612500Y1562000D03*
G01X358500Y1562000D03*
G01X612500Y1816000D03*
G01X358500Y1816000D03*
G01X612500Y2070000D03*
G01X358500Y2070000D03*
G01X612500Y2324000D03*
G01X358500Y2324000D03*
G01X612500Y2578000D03*
G01X358500Y2578000D03*
G54D19*
G01X612500Y2832000D03*
G54D17*
G01X358500Y2832000D03*
G54D19*
G01X612500Y3086000D03*
G54D17*
G01X358500Y3086000D03*
G01X612500Y3340000D03*
G01X358500Y3340000D03*
G01X612500Y3594000D03*
G01X358500Y3594000D03*
G01X612500Y3848000D03*
G01X358500Y3848000D03*
G01X612500Y4102000D03*
G01X358500Y4102000D03*
G01X612500Y4356000D03*
G01X358500Y4356000D03*
G01X612500Y4610000D03*
G01X358500Y4610000D03*
G01X612500Y4864000D03*
G01X358500Y4864000D03*
G01X612500Y5118000D03*
G01X358500Y5118000D03*
G01X612500Y5372000D03*
G01X358500Y5372000D03*
G54D10*
G01X3829410Y6008016D03*
G01X3575410Y6008016D03*
G01X3829410Y6262016D03*
G01X3575410Y6262016D03*
G01X3829410Y6516016D03*
G01X3575410Y6516016D03*
G01X3829410Y6770016D03*
G01X3575410Y6770016D03*
G01X3829410Y7024016D03*
G01X3575410Y7024016D03*
G01X3829410Y7278016D03*
G01X3575410Y7278016D03*
G01X3829410Y7532016D03*
G01X3575410Y7532016D03*
G01X3829410Y7786016D03*
G01X3575410Y7786016D03*
G01X3829410Y8040016D03*
G01X3575410Y8040016D03*
G01X3829410Y8294016D03*
G54D11*
G01X3575410Y8294016D03*
G54D20*
G01X3550000Y9619000D03*
G54D21*
G01X3296000Y9619000D03*
G01X3550000Y9873000D03*
G01X3296000Y9873000D03*
G01X3550000Y10127000D03*
G01X3296000Y10127000D03*
G01X3550000Y10381000D03*
G01X3296000Y10381000D03*
G01X3550000Y10635000D03*
G01X3296000Y10635000D03*
G54D10*
G01X2091542Y10008008D03*
G01X2091542Y9754008D03*
G01X1837542Y10008008D03*
G01X1837542Y9754008D03*
G01X1583542Y10008008D03*
G01X1583542Y9754008D03*
G01X1329542Y10008008D03*
G01X1329542Y9754008D03*
G01X1075542Y10008008D03*
G54D22*
G01X1075542Y9754008D03*
G54D20*
G01X4550000Y9619000D03*
G54D21*
G01X4296000Y9619000D03*
G01X4550000Y9873000D03*
G01X4296000Y9873000D03*
G01X4550000Y10127000D03*
G01X4296000Y10127000D03*
G01X4550000Y10381000D03*
G01X4296000Y10381000D03*
G01X4550000Y10635000D03*
G01X4296000Y10635000D03*
G54D22*
G01X-2308500Y10782200D03*
G54D10*
G01X-2054500Y10782200D03*
G01X-1800500Y10782200D03*
G01X-1546500Y10782200D03*
G54D23*
G01X1577700Y4210102D03*
G01X1577700Y3340102D03*
G04 Pad End*
M02*
+99
View File
@@ -0,0 +1,99 @@
G04 Layer: DocumentLayer*
G04 EasyEDA Pro v3.2.148, 2026-08-18 14:30:59*
G04 Gerber Generator version 0.3*
G04 Scale: 100 percent, Rotated: No, Reflected: No*
G04 Dimensions in millimeters*
G04 Leading zeros omitted, absolute positions, 4 integers and 5 decimals*
G04 Generated by one-click*
G04 Object Configs: Pad,Via,Track,Text,Image,Dimension,BoardOutline,BoardCutout,CopperFilled,SolidRegion,FPCStiffener,Line,PlaneZone,ComponentProperty,ComponentSilkscreen,TearDrop*
G04 Layer Configs: 1,2,3,4,5,6,7,8,9,10,11,13,14,56*
G04 Copper Areas: 0*
%FSLAX45Y45*%
%MOMM*%
%ADD10C,0.254*%
%ADD11C,0.2978*%
G75*
G04 PolygonModel Start*
G36*
G01X904600Y2705000D02*
G01X739500Y2705000D01*
G01X739500Y3213000D01*
G01X904600Y3213000D01*
G01X904600Y2705000D01*
G37*
G36*
G01X3715100Y9911100D02*
G01X3715100Y10368300D01*
G01X3867500Y10368300D01*
G01X3867500Y9911100D01*
G01X3715100Y9911100D01*
G37*
G36*
G01X4715100Y9911100D02*
G01X4715100Y10368300D01*
G01X4867500Y10368300D01*
G01X4867500Y9911100D01*
G01X4715100Y9911100D01*
G37*
G36*
G01X2130399Y6247599D02*
G01X2130399Y6223601D01*
G01X2034400Y6223601D01*
G01X2034400Y6247599D01*
G01X2130399Y6247599D01*
G37*
G36*
G01X1558401Y6247599D02*
G01X1654400Y6247599D01*
G01X1654400Y6223601D01*
G01X1558401Y6223601D01*
G01X1558401Y6247599D01*
G37*
G36*
G01X1594400Y6315600D02*
G01X1618401Y6315600D01*
G01X1618401Y6155600D01*
G01X1594400Y6155600D01*
G01X1594400Y6315600D01*
G37*
G36*
G01X1534401Y6395600D02*
G01X1582399Y6395600D01*
G01X1582399Y6315600D01*
G01X1534401Y6315600D01*
G01X1534401Y6395600D01*
G37*
G36*
G01X1534401Y6075600D02*
G01X1534401Y6155600D01*
G01X1582399Y6155600D01*
G01X1582399Y6075600D01*
G01X1534401Y6075600D01*
G37*
G36*
G01X1753620Y3433614D02*
G01X1753620Y3255814D01*
G01X1702820Y3255814D01*
G01X1702820Y3433614D01*
G01X1753620Y3433614D01*
G37*
G36*
G01X1636968Y3370584D02*
G01X1814768Y3370584D01*
G01X1814768Y3319784D01*
G01X1636968Y3319784D01*
G01X1636968Y3370584D01*
G37*
G36*
G01X1722828Y4285332D02*
G01X1722828Y4107532D01*
G01X1672028Y4107532D01*
G01X1672028Y4285332D01*
G01X1722828Y4285332D01*
G37*
G54D10*
M02*
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,25 @@
G04 Layer: TopPasteMaskLayer*
G04 EasyEDA Pro v3.2.148, 2026-08-18 14:30:59*
G04 Gerber Generator version 0.3*
G04 Scale: 100 percent, Rotated: No, Reflected: No*
G04 Dimensions in millimeters*
G04 Leading zeros omitted, absolute positions, 4 integers and 5 decimals*
G04 Generated by one-click*
G04 Object Configs: Pad,Via,Track,Text,Image,Dimension,BoardOutline,BoardCutout,CopperFilled,SolidRegion,FPCStiffener,Line,PlaneZone,ComponentProperty,ComponentSilkscreen,TearDrop*
G04 Layer Configs: 1,2,3,4,5,6,7,8,9,10,11,13,14,56*
G04 Copper Areas: 0*
%FSLAX45Y45*%
%MOMM*%
%ADD10R,1.9X2.37599*%
%ADD11C,0.8733*%
G75*
G04 Pad Start*
G54D10*
G01X1584406Y6235600D03*
G01X2104394Y6235600D03*
G04 Pad End*
M02*
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,258 @@
G04 Layer: TopSolderMaskLayer*
G04 EasyEDA Pro v3.2.148, 2026-08-18 14:30:59*
G04 Gerber Generator version 0.3*
G04 Scale: 100 percent, Rotated: No, Reflected: No*
G04 Dimensions in millimeters*
G04 Leading zeros omitted, absolute positions, 4 integers and 5 decimals*
G04 Generated by one-click*
G04 Object Configs: Pad,Via,Track,Text,Image,Dimension,BoardOutline,BoardCutout,CopperFilled,SolidRegion,FPCStiffener,Line,PlaneZone,ComponentProperty,ComponentSilkscreen,TearDrop*
G04 Layer Configs: 1,2,3,4,5,6,7,8,9,10,11,13,14,56*
G04 Copper Areas: 0*
%FSLAX45Y45*%
%MOMM*%
%AMRoundRect*1,1,$1,$2,$3*1,1,$1,$4,$5*1,1,$1,0-$2,0-$3*1,1,$1,0-$4,0-$5*20,1,$1,$2,$3,$4,$5,0*20,1,$1,$4,$5,0-$2,0-$3,0*20,1,$1,0-$2,0-$3,0-$4,0-$5,0*20,1,$1,0-$4,0-$5,$2,$3,0*4,1,4,$2,$3,$4,$5,0-$2,0-$3,0-$4,0-$5,$2,$3,0*%
%ADD10C,1.7526*%
%ADD11RoundRect,0.1016X-0.8255X-0.8255X-0.8255X0.8255*%
%ADD12O,2.0066X1.4986*%
%ADD13RoundRect,0.1016X-0.9525X0.6985X0.9525X0.6985*%
%ADD14C,1.8542*%
%ADD15C,1.7037*%
%ADD16C,1.7016*%
%ADD17RoundRect,0.1016X-0.8X-0.8X-0.8X0.8*%
%ADD18C,1.4986*%
%ADD19RoundRect,0.1016X-0.762X-0.762X-0.762X0.762*%
%ADD20C,1.6256*%
%ADD21RoundRect,0.1016X-0.8255X0.8255X0.8255X0.8255*%
%ADD22RoundRect,0.1016X-0.95X1.188X0.95X1.188*%
%ADD23C,1.9016*%
G75*
G04 Pad Start*
G54D10*
G01X1196700Y3594000D03*
G54D11*
G01X1196700Y3848000D03*
G54D12*
G01X2733400Y12572900D03*
G01X2733400Y12318900D03*
G01X2733400Y12064900D03*
G01X2733400Y11810900D03*
G01X2733400Y11556900D03*
G01X2733400Y11302900D03*
G01X2733400Y11048900D03*
G01X2733400Y10794900D03*
G01X2733400Y10540900D03*
G01X2733400Y10286900D03*
G01X2733400Y10032900D03*
G01X2733400Y9778900D03*
G01X2733400Y9524900D03*
G01X2733400Y9270900D03*
G01X2733400Y9016900D03*
G01X2733400Y8762900D03*
G01X2733400Y8508900D03*
G01X2733400Y8254900D03*
G01X2733400Y8000900D03*
G01X2733400Y7746900D03*
G01X2733400Y7492900D03*
G01X2733400Y7238900D03*
G01X193400Y7238900D03*
G01X193400Y7492900D03*
G01X193400Y7746900D03*
G01X193400Y8000900D03*
G01X193400Y8254900D03*
G01X193400Y8508900D03*
G01X193400Y8762900D03*
G01X193400Y9016900D03*
G01X193400Y9270900D03*
G01X193400Y9524900D03*
G01X193400Y9778900D03*
G01X193400Y10032900D03*
G01X193400Y10286900D03*
G01X193400Y10540900D03*
G01X193400Y10794900D03*
G01X193400Y11048900D03*
G01X193400Y11302900D03*
G01X193400Y11556900D03*
G01X193400Y11810900D03*
G01X193400Y12064900D03*
G01X193400Y12318900D03*
G54D13*
G01X193400Y12572900D03*
G54D14*
G01X-276500Y7492900D03*
G01X-276500Y7746900D03*
G01X-276500Y8000900D03*
G01X-276500Y8254900D03*
G01X-276500Y8508900D03*
G01X-276500Y8762900D03*
G01X-276500Y9016900D03*
G01X-276500Y9270900D03*
G01X-276500Y9524900D03*
G01X-2308500Y9016900D03*
G01X-2308500Y8762900D03*
G01X-2308500Y8508900D03*
G01X-2308500Y8254900D03*
G01X-2308500Y8000900D03*
G01X-2308500Y7746900D03*
G54D16*
G01X1946000Y5372000D03*
G01X2200000Y5372000D03*
G01X1946000Y5118000D03*
G01X2200000Y5118000D03*
G01X1946000Y4864000D03*
G01X2200000Y4864000D03*
G01X1946000Y4610000D03*
G01X2200000Y4610000D03*
G01X1946000Y4356000D03*
G01X2200000Y4356000D03*
G01X1946000Y4102000D03*
G01X2200000Y4102000D03*
G01X1946000Y3848000D03*
G01X2200000Y3848000D03*
G01X1946000Y3594000D03*
G01X2200000Y3594000D03*
G01X1946000Y3340000D03*
G01X2200000Y3340000D03*
G01X1946000Y3086000D03*
G01X2200000Y3086000D03*
G01X1946000Y2832000D03*
G01X2200000Y2832000D03*
G01X1946000Y2578000D03*
G01X2200000Y2578000D03*
G01X1946000Y2324000D03*
G01X2200000Y2324000D03*
G01X1946000Y2070000D03*
G01X2200000Y2070000D03*
G01X1946000Y1816000D03*
G01X2200000Y1816000D03*
G01X1946000Y1562000D03*
G01X2200000Y1562000D03*
G01X1946000Y1308000D03*
G01X2200000Y1308000D03*
G01X1946000Y1054000D03*
G01X2200000Y1054000D03*
G01X1946000Y800000D03*
G01X2200000Y800000D03*
G01X1946000Y546000D03*
G54D17*
G01X2200000Y546000D03*
G01X612500Y546000D03*
G54D16*
G01X358500Y546000D03*
G01X612500Y800000D03*
G01X358500Y800000D03*
G01X612500Y1054000D03*
G01X358500Y1054000D03*
G01X612500Y1308000D03*
G01X358500Y1308000D03*
G01X612500Y1562000D03*
G01X358500Y1562000D03*
G01X612500Y1816000D03*
G01X358500Y1816000D03*
G01X612500Y2070000D03*
G01X358500Y2070000D03*
G01X612500Y2324000D03*
G01X358500Y2324000D03*
G01X612500Y2578000D03*
G01X358500Y2578000D03*
G54D18*
G01X612500Y2832000D03*
G54D16*
G01X358500Y2832000D03*
G54D18*
G01X612500Y3086000D03*
G54D16*
G01X358500Y3086000D03*
G01X612500Y3340000D03*
G01X358500Y3340000D03*
G01X612500Y3594000D03*
G01X358500Y3594000D03*
G01X612500Y3848000D03*
G01X358500Y3848000D03*
G01X612500Y4102000D03*
G01X358500Y4102000D03*
G01X612500Y4356000D03*
G01X358500Y4356000D03*
G01X612500Y4610000D03*
G01X358500Y4610000D03*
G01X612500Y4864000D03*
G01X358500Y4864000D03*
G01X612500Y5118000D03*
G01X358500Y5118000D03*
G01X612500Y5372000D03*
G01X358500Y5372000D03*
G54D10*
G01X3829410Y6008016D03*
G01X3575410Y6008016D03*
G01X3829410Y6262016D03*
G01X3575410Y6262016D03*
G01X3829410Y6516016D03*
G01X3575410Y6516016D03*
G01X3829410Y6770016D03*
G01X3575410Y6770016D03*
G01X3829410Y7024016D03*
G01X3575410Y7024016D03*
G01X3829410Y7278016D03*
G01X3575410Y7278016D03*
G01X3829410Y7532016D03*
G01X3575410Y7532016D03*
G01X3829410Y7786016D03*
G01X3575410Y7786016D03*
G01X3829410Y8040016D03*
G01X3575410Y8040016D03*
G01X3829410Y8294016D03*
G54D11*
G01X3575410Y8294016D03*
G54D19*
G01X3550000Y9619000D03*
G54D20*
G01X3296000Y9619000D03*
G01X3550000Y9873000D03*
G01X3296000Y9873000D03*
G01X3550000Y10127000D03*
G01X3296000Y10127000D03*
G01X3550000Y10381000D03*
G01X3296000Y10381000D03*
G01X3550000Y10635000D03*
G01X3296000Y10635000D03*
G54D10*
G01X2091542Y10008008D03*
G01X2091542Y9754008D03*
G01X1837542Y10008008D03*
G01X1837542Y9754008D03*
G01X1583542Y10008008D03*
G01X1583542Y9754008D03*
G01X1329542Y10008008D03*
G01X1329542Y9754008D03*
G01X1075542Y10008008D03*
G54D21*
G01X1075542Y9754008D03*
G54D19*
G01X4550000Y9619000D03*
G54D20*
G01X4296000Y9619000D03*
G01X4550000Y9873000D03*
G01X4296000Y9873000D03*
G01X4550000Y10127000D03*
G01X4296000Y10127000D03*
G01X4550000Y10381000D03*
G01X4296000Y10381000D03*
G01X4550000Y10635000D03*
G01X4296000Y10635000D03*
G54D21*
G01X-2308500Y10782200D03*
G54D10*
G01X-2054500Y10782200D03*
G01X-1800500Y10782200D03*
G01X-1546500Y10782200D03*
G54D22*
G01X1584406Y6235600D03*
G01X2104394Y6235600D03*
G54D23*
G01X1577700Y4210102D03*
G01X1577700Y3340102D03*
G04 Pad End*
M02*
+4
View File
@@ -0,0 +1,4 @@
How to Order PCB
Please refer to:
https://prodocs.easyeda.com/en/pcb/order-order-pcb/index.html
Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

+3
View File
@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(spi_gpio_probe)
+1
View File
@@ -0,0 +1 @@
idf_component_register(SRCS "main.c" INCLUDE_DIRS ".")
+103
View File
@@ -0,0 +1,103 @@
// Standalone diagnostic firmware - NOT part of the real esp-spi project.
// Purpose: bypass the SPI slave peripheral entirely and watch the raw GPIO
// pins (plain digital input, interrupt on every edge) that our real
// firmware's spi_slave.cpp configures for SPI slave use on ESP32-S3:
// CS=GPIO10, SCLK=GPIO12, MOSI=GPIO13, MISO=GPIO11
// Logs every CS/SCLK edge with a microsecond timestamp and the
// MOSI/MISO/CS level sampled at that instant, so a real transaction
// attempt from the CH341A bridge can be visually reconstructed from the
// console log - answers directly whether the ESP32 sees anything at all
// on these pins, and if so, whether it looks like a sane SPI waveform
// (CS low, some number of SCLK edges, CS high) or not.
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "esp_timer.h"
#define PIN_CS 10
#define PIN_SCLK 12
#define PIN_MOSI 13
#define PIN_MISO 11
typedef struct
{
int64_t us;
uint8_t pin; // which pin's edge triggered this event (PIN_CS or PIN_SCLK)
uint8_t level; // level that pin transitioned TO
uint8_t cs;
uint8_t sclk;
uint8_t mosi;
uint8_t miso;
} event_t;
#define EVQ_LEN 4096
static QueueHandle_t evq;
static void IRAM_ATTR gpio_isr(void *arg)
{
uint32_t pin = (uint32_t)(uintptr_t)arg;
event_t ev;
ev.us = esp_timer_get_time();
ev.pin = (uint8_t)pin;
ev.level = (uint8_t)gpio_get_level((gpio_num_t)pin);
ev.cs = (uint8_t)gpio_get_level(PIN_CS);
ev.sclk = (uint8_t)gpio_get_level(PIN_SCLK);
ev.mosi = (uint8_t)gpio_get_level(PIN_MOSI);
ev.miso = (uint8_t)gpio_get_level(PIN_MISO);
BaseType_t hpw = pdFALSE;
xQueueSendFromISR(evq, &ev, &hpw);
if (hpw) portYIELD_FROM_ISR();
}
void app_main(void)
{
printf("\n\n=== spi_gpio_probe starting ===\n");
printf("Pins: CS=GPIO%d SCLK=GPIO%d MOSI=GPIO%d MISO=GPIO%d\n", PIN_CS, PIN_SCLK, PIN_MOSI, PIN_MISO);
printf("Watching CS + SCLK edges only (plain digital input, no SPI peripheral involved).\n\n");
evq = xQueueCreate(EVQ_LEN, sizeof(event_t));
gpio_config_t io = { 0 };
io.pin_bit_mask = (1ULL << PIN_CS) | (1ULL << PIN_SCLK) | (1ULL << PIN_MOSI) | (1ULL << PIN_MISO);
io.mode = GPIO_MODE_INPUT;
io.pull_up_en = GPIO_PULLUP_DISABLE;
io.pull_down_en = GPIO_PULLDOWN_DISABLE;
io.intr_type = GPIO_INTR_ANYEDGE;
gpio_config(&io);
gpio_install_isr_service(0);
gpio_isr_handler_add((gpio_num_t)PIN_CS, gpio_isr, (void*)(uintptr_t)PIN_CS);
gpio_isr_handler_add((gpio_num_t)PIN_SCLK, gpio_isr, (void*)(uintptr_t)PIN_SCLK);
printf("Idle levels right now: CS=%d SCLK=%d MOSI=%d MISO=%d\n\n",
gpio_get_level(PIN_CS), gpio_get_level(PIN_SCLK),
gpio_get_level(PIN_MOSI), gpio_get_level(PIN_MISO));
int64_t last_us = 0;
unsigned long total_events = 0;
unsigned long cs_high_count = 0;
for (;;)
{
event_t ev;
if (xQueueReceive(evq, &ev, pdMS_TO_TICKS(3000)))
{
const char *pin_name = (ev.pin == PIN_CS) ? "CS " : "SCLK";
long long delta = last_us ? (long long)(ev.us - last_us) : 0;
printf("[t=%10lld us d=%6lld us] %s -> %d | CS=%d SCLK=%d MOSI=%d MISO=%d\n",
(long long)ev.us, delta, pin_name, ev.level,
ev.cs, ev.sclk, ev.mosi, ev.miso);
last_us = ev.us;
total_events++;
if (ev.pin == PIN_CS && ev.level == 1) cs_high_count++;
}
else
{
printf("--- idle (no CS/SCLK edges in last 3s) --- total_events=%lu cs_high_transitions=%lu\n",
total_events, cs_high_count);
}
}
}
File diff suppressed because it is too large Load Diff
+22
View File
@@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(EXTRA_COMPONENT_DIRS
"${CMAKE_CURRENT_LIST_DIR}/main"
"${CMAKE_CURRENT_LIST_DIR}/components/libsmb2_esp"
)
project(esp-spi)
set(app_elf "${PROJECT_BINARY_DIR}/${PROJECT_NAME}.elf")
set(app_disasm "${PROJECT_BINARY_DIR}/${PROJECT_NAME}.s")
add_custom_command(
OUTPUT "${app_disasm}"
COMMAND "${CMAKE_OBJDUMP}" -S "${app_elf}" > "${app_disasm}"
DEPENDS "${app_elf}"
VERBATIM
)
add_custom_target(disasm ALL DEPENDS "${app_disasm}")
+58
View File
@@ -0,0 +1,58 @@
# WEATHER_GET по SPI
Команда `ESP_CMD_WEATHER_GET = 0x2D`. Код `0x24` исходного ZiFi занят в этом
проекте командой HTTPS_STREAM_START; UART-кадр ответа `0xA4` здесь не используется.
Сервисы: api.zippopotam.us и api.open-meteo.com, прямое HTTP-соединение.
1. Подключить Wi-Fi обычными командами проекта.
2. Создать объект DATA или DATAF через MAKE_OBJECT и записать через WRITE_OBJECT
две строки с завершающими нулями: код страны ISO из двух латинских букв и индекс.
Например, `IT\0` + `00144\0` — 9 байт. Индекс: от 1 до 31 байта.
Допускается нулевое дополнение объекта для выравнивания DMA.
3. Записать handle входного объекта в ESP_REG_OBJ_HANDLE и выполнить `0x2D`.
Дождаться завершения BUSY; тайм-аут на стороне хоста рекомендуется 90 секунд.
4. При READY: ESP_REG_OBJ_HANDLE содержит новый объект DATA,
ESP_REG_DATA_OFFSET=0, ESP_REG_DATA_SIZE=90. Прочитать через READ_OBJECT.
5. Удалить входной и выходной объекты через DELETE_OBJECT, когда они больше не нужны.
Входной объект команда не изменяет и не удаляет; его handle следует сохранить.
При ошибке новый объект не создаётся, входной handle сохраняется, DATA_SIZE=0.
Статусы: INV_HANDLE, INV_PARAM, INV_STR_LEN, AP_NOT_CONNECTED,
OUT_OF_MEMORY/OUT_OF_HANDLES; `ESP_ERR_WEATHER=0xB5` означает ошибку HTTP или
разбора JSON (подробность в логе `weather`). Без Wi-Fi команда даёт INV_COMMAND.
Координаты кешируются до изменения пары страна/индекс или перезагрузки.
Прогноз запрашивается при каждом вызове. Для обрывов, 429 и 5xx — максимум три
попытки, паузы 1 и 2 секунды; новые запросы начинаются только в первые 30 секунд.
Ответы ограничены 4095 байтами, усечённые ответы не принимаются.
Параметры из zifi.ini передаёт хост: прошивка сама этот файл не читает.
Прокси из оригинального ZiFi не перенесён: в данном SPI API нет его настройки.
## Запись версии 1 (90 байт)
Все 16-битные числа little-endian. Температуры — знаковые байты в °C.
| Смещение | Размер | Поле |
|---|---|---|
| 0 | 1 | 1: данные получены |
| 1 | 1 | Версия: 1 |
| 2 | 24 | Название места, CP866, с нулём |
| 26 | 1 | Текущая температура |
| 27 | 1 | Код WMO |
| 28 | 1 | 1: день |
| 29 | 2 | Ветер, км/ч ×10 |
| 31 | 2 | Осадки за час, мм ×10 |
| 33 | 2 | Давление, мм рт. ст. |
| 35 | 2 | Восход: час, минута |
| 37 | 2 | Закат: час, минута |
| 39 | 2 | Местное время данных: час, минута |
| 41 | 1 | Число дневных записей, до 6 |
| 42 | 48 | До 6 записей по 8 байт |
Дневная запись: число, месяц, день недели (0=понедельник), код WMO,
tmin, tmax, осадки мм ×10 (u16). Первый день — сегодня.
Парсер `main/weather_parse.cpp` и `main/zifi/weather_parse.hpp` перенесён из
локального ZiFi-ESP32-S3-Zero; формат совместим с коммитом
[616bee6](https://github.com/andrewinsidelazarev/ZiFi-ESP32-S3-Zero/commit/616bee6333d34dfd4dfd83ffb4bb8a44c2c974f9).
Проверка парсера: `python tests/test_weather_parse.py` (нужен MSVC).
+28
View File
@@ -0,0 +1,28 @@
#!/bin/sh
set -eu
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
HDR="${1:-$SCRIPT_DIR/../src/esp_spi_defs.h}"
if [ ! -f "$HDR" ]; then
echo "bump-api: file not found: $HDR" >&2
exit 1
fi
old="$(sed -nE 's/^[[:space:]]*#define[[:space:]]+API_VER[[:space:]]+([0-9]+).*/\1/p' "$HDR" | head -n1)"
if [ -z "$old" ]; then
echo "bump-api: API_VER not found in $HDR" >&2
exit 1
fi
if [ "$old" -ge 255 ]; then
echo "bump-api: API_VER is 1 byte and already at maximum 255" >&2
exit 1
fi
new=$((old + 1))
sed -i -E "s/^([[:space:]]*#define[[:space:]]+API_VER[[:space:]]+)[0-9]+/\1${new}/" "$HDR"
echo "bump-api: API ${old} -> ${new}"
+28
View File
@@ -0,0 +1,28 @@
#!/bin/sh
set -eu
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
HDR="${1:-$SCRIPT_DIR/../src/esp_spi_defs.h}"
if [ ! -f "$HDR" ]; then
echo "bump-feat: file not found: $HDR" >&2
exit 1
fi
old="$(sed -nE 's/^[[:space:]]*#define[[:space:]]+FEAT_VER[[:space:]]+([0-9]+).*/\1/p' "$HDR" | head -n1)"
if [ -z "$old" ]; then
echo "bump-feat: FEAT_VER not found in $HDR" >&2
exit 1
fi
if [ "$old" -ge 65535 ]; then
echo "bump-feat: FEAT_VER is 2 bytes and already at maximum 65535" >&2
exit 1
fi
new=$((old + 1))
sed -i -E "s/^([[:space:]]*#define[[:space:]]+FEAT_VER[[:space:]]+)[0-9]+/\1${new}/" "$HDR"
echo "bump-feat: FEAT ${old} -> ${new}"
+26
View File
@@ -0,0 +1,26 @@
#!/bin/sh
set -eu
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
HDR="${1:-$SCRIPT_DIR/../src/esp_spi_defs.h}"
if [ ! -f "$HDR" ]; then
echo "bump: file not found: $HDR" >&2
exit 1
fi
set -- $(sed -nE 's/^[[:space:]]*#define[[:space:]]+PROD_VER([0-2])[[:space:]]+([0-9]+).*/\1 \2/p' "$HDR" | sort | awk '{print $2}')
if [ "$#" -ne 3 ]; then
echo "bump: PROD_VER0..2 not found in $HDR" >&2
exit 1
fi
major="$1"
minor="$2"
old="$3"
new=$((old + 1))
sed -i -E "s/^([[:space:]]*#define[[:space:]]+PROD_VER2[[:space:]]+)[0-9]+/\1${new}/" "$HDR"
echo "bump: ver.${major}.${minor}.${old} -> ver.${major}.${minor}.${new}"
+103
View File
@@ -0,0 +1,103 @@
# Modern ESP-IDF (idf_component_register) wrapper around the vendored
# libsmb2 checkout at E:\esp32\src\third_party\libsmb2 (upstream
# sahlberg/libsmb2, LGPL-2.1). Vendored outside components/ deliberately:
# ESP-IDF auto-scans every immediate subdirectory of components/ as a
# candidate component, and libsmb2's own CMakeLists.txt predates
# idf_component_register (calls add_definitions() at the top level, which
# ESP-IDF 6.1's two-pass component requirement scan rejects with
# "add_definitions command is not scriptable") — so it must never be
# auto-discovered directly. Source list and compile definitions below are
# copied from the upstream lib/CMakeLists.txt ESP_PLATFORM branch, minus
# aes_apple.c/krb5-wrapper.c (excluded there too for this platform).
set(LIBSMB2_DIR "${CMAKE_CURRENT_LIST_DIR}/../../third_party/libsmb2")
set(LIBSMB2_LIB_DIR "${LIBSMB2_DIR}/lib")
set(LIBSMB2_SOURCES
aes.c
aes_reference.c
aes128ccm.c
alloc.c
asn1-ber.c
compat.c
libsmb2-dcerpc.c
libsmb2-dcerpc-srvsvc.c
errors.c
hmac.c
hmac-md5.c
init.c
libsmb2.c
md4c.c
md5.c
ntlmssp.c
pdu.c
sha1.c
sha224-256.c
sha384-512.c
smb2-cmd-close.c
smb2-cmd-create.c
smb2-cmd-echo.c
smb2-cmd-error.c
smb2-cmd-flush.c
smb2-cmd-ioctl.c
smb2-cmd-lock.c
smb2-cmd-logoff.c
smb2-cmd-negotiate.c
smb2-cmd-notify-change.c
smb2-cmd-oplock-break.c
smb2-cmd-query-directory.c
smb2-cmd-query-info.c
smb2-cmd-read.c
smb2-cmd-session-setup.c
smb2-cmd-set-info.c
smb2-cmd-tree-connect.c
smb2-cmd-tree-disconnect.c
smb2-cmd-write.c
smb2-data-file-info.c
smb2-data-filesystem-info.c
smb2-data-security-descriptor.c
smb2-data-reparse-point.c
smb2-share-enum.c
smb3-seal.c
smb2-signing.c
socket.c
spnego-wrapper.c
sync.c
timestamps.c
unicode.c
usha.c
)
list(TRANSFORM LIBSMB2_SOURCES PREPEND "${LIBSMB2_LIB_DIR}/")
idf_component_register(
SRCS ${LIBSMB2_SOURCES}
INCLUDE_DIRS
"${LIBSMB2_DIR}/include"
"${LIBSMB2_DIR}/include/smb2"
"${LIBSMB2_DIR}/include/esp"
PRIV_INCLUDE_DIRS
"${LIBSMB2_LIB_DIR}"
REQUIRES
lwip
mbedtls
sock_utils
)
target_compile_definitions(${COMPONENT_LIB} PRIVATE
HAVE_CONFIG_H
NEED_READV
NEED_WRITEV
NEED_GETLOGIN_R
NEED_RANDOM
NEED_SRANDOM
"_U_=__attribute__((unused))"
)
# Vendored upstream code, built as-is: relax -Werror for warning classes that
# are harmless in practice (e.g. a tautological unsigned comparison in
# libsmb2.c) rather than patching third-party sources to satisfy this
# project's stricter default warning flags.
target_compile_options(${COMPONENT_LIB} PRIVATE
-Wno-error=type-limits
)
@@ -0,0 +1,8 @@
## IDF Component Manager Manifest File
dependencies:
idf:
version: '>=4.1.0'
# gethostname() is not provided by ESP-IDF's libc; libsmb2's server accept
# path (smb2_serve_port) calls it directly. sock_utils supplies a minimal
# implementation.
espressif/sock_utils: "*"
+83
View File
@@ -0,0 +1,83 @@
dependencies:
espressif/esp_new_jpeg:
component_hash: e1e7e6991193c41ed6c9fc386702aa7821433319e7aa9c3147cd3236310cd69c
dependencies: []
source:
registry_url: https://components.espressif.com/
type: service
targets:
- esp32
- esp32s2
- esp32s3
- esp32s31
- esp32p4
- esp32c2
- esp32c3
- esp32c5
- esp32c6
- esp32c61
- esp32h4
version: 1.0.2
espressif/sock_utils:
component_hash: b2e5cafc60dfefd05720ababb9fca0dfe2830fb9d71f97e5336de36a84c591c9
dependencies:
- name: idf
require: private
version: '>=5.0'
source:
registry_url: https://components.espressif.com/
type: service
version: 0.2.2
espressif/usb:
component_hash: 6c2cff118141dbbca181d0d27d71801fdddfc78f7ac03ae43f6e5a8cbd89cb99
dependencies:
- name: idf
require: private
version: '>=5.5.3'
source:
registry_url: https://components.espressif.com
type: service
targets:
- esp32s2
- esp32s3
- esp32s31
- esp32p4
- esp32h4
- linux
version: 1.5.0
espressif/usb_host_hid:
component_hash: a6c5366f9b6d80b0d5f56d085d62941464512e0968b96c5877e76a80fcc23f13
dependencies:
- name: idf
require: private
version: '>=5.0'
- name: espressif/usb
registry_url: https://components.espressif.com
require: public
rules:
- if: idf_version >=6.0
- if: target not in ["linux"]
version: ^1.0.0
source:
registry_url: https://components.espressif.com/
type: service
targets:
- esp32s2
- esp32s3
- esp32p4
- esp32h4
- esp32s31
- linux
version: 1.2.0
idf:
source:
type: idf
version: 6.1.0
direct_dependencies:
- espressif/esp_new_jpeg
- espressif/sock_utils
- espressif/usb_host_hid
- idf
manifest_hash: a4d460bb515c6dffc30079d44d3c29fccc46d49abbe9d3c15eabf6486c7456c0
target: esp32s3
version: 3.0.0
+12
View File
@@ -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)
+314
View File
@@ -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).
}
}
+22
View File
@@ -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);
+572
View File
@@ -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);
}
+30
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+696
View File
@@ -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
+179
View File
@@ -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);
}
}
+6
View File
@@ -0,0 +1,6 @@
#pragma once
void initialize_console();
void console_task(void *arg);
void esp_console_register_system_commands();
+359
View File
@@ -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);
}
+6
View File
@@ -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);
+244
View File
@@ -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 = &section_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 = &section_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 = &section_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 = &section_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 = &section_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 = &section_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;
}
+4
View File
@@ -0,0 +1,4 @@
#pragma once
int load_elf(u8 *elf, MEM_OBJ *obj, int opt);
+375
View File
@@ -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
};
+618
View File
@@ -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));
}
+35
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+584
View File
@@ -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();
+252
View File
@@ -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);
}
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
void gopher_init(void);
void gopher_do_get(void);
+806
View File
@@ -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;
}
}
}
}
+83
View File
@@ -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);
+184
View File
@@ -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) {}
+14
View File
@@ -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
+18
View File
@@ -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
+238
View File
@@ -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
}
+58
View File
@@ -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);
+231
View File
@@ -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;
}
+37
View File
@@ -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);
+466
View File
@@ -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;
}
+59
View File
@@ -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);
+149
View File
@@ -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;
}
+22
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+87
View File
@@ -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
+922
View File
@@ -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;
}
+8
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+99
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+75
View File
@@ -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
+60
View File
@@ -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);
+80
View File
@@ -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;
}
};
+60
View File
@@ -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();
};
+502
View File
@@ -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);
}
+8
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+13366
View File
File diff suppressed because it is too large Load Diff
+932
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+50
View File
@@ -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();
+517
View File
@@ -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;
}
+177
View File
@@ -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
+434
View File
@@ -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;
}
+7
View File
@@ -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
+21
View File
@@ -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();
+124
View File
@@ -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();
}
+65
View File
@@ -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);
+243
View File
@@ -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"
"&current=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);
}
+4
View File
@@ -0,0 +1,4 @@
#pragma once
// Called only by the serialized helper task.
void weather_do_get();
+723
View File
@@ -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

Some files were not shown because too many files have changed in this diff Show More