├── main ├── ethernet.h ├── recovery_listener.h ├── recovery.h ├── component.mk ├── net_common.h ├── wifi.h ├── scan_manager.h ├── pstrings.h ├── storage.h ├── pstrings.c ├── led.h ├── CMakeLists.txt ├── net_common.c ├── packet_types.h ├── uart.h ├── crc32.h ├── ip.h ├── apps.h ├── recovery_listener.c ├── buffer_pool.h ├── crc.h ├── tashtalk.h ├── node_table.c ├── node_table.h ├── hw.h ├── packet_utils.h ├── ethernet.c ├── main.c ├── html_page.h ├── wifi.c ├── crc.c ├── storage.c ├── buffer_pool.c ├── scan_manager.c ├── crc32.c ├── recovery.c ├── uart.c ├── led.c ├── tashtalk.c ├── packet_utils.c ├── apps.c └── ip.c ├── README.md ├── hardware ├── cpl │ └── board-CPL.csv ├── easyeda-project.zip ├── boards │ ├── gerber-top.zip │ ├── gerber-bottom.zip │ ├── gerber-mainboard.zip │ ├── README.md │ ├── easyeda-top.json │ └── easyeda-bottom.json ├── bom │ ├── BOM_PCB_airtalk_v2a_2023-02-11.csv │ └── README.md ├── schematic │ └── Schematic_airtalk_v1_2023-02-11.pdf ├── README.md └── LICENSE ├── Makefile ├── CMakeLists.txt ├── .gitignore └── LICENSE /main/ethernet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void ethernet_init(void); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # airtalk 2 | Firmware for the airtalk dongle. 3 | 4 | Hardware coming soon -------------------------------------------------------------------------------- /hardware/cpl/board-CPL.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cheesestraws/airtalk/HEAD/hardware/cpl/board-CPL.csv -------------------------------------------------------------------------------- /hardware/easyeda-project.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cheesestraws/airtalk/HEAD/hardware/easyeda-project.zip -------------------------------------------------------------------------------- /hardware/boards/gerber-top.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cheesestraws/airtalk/HEAD/hardware/boards/gerber-top.zip -------------------------------------------------------------------------------- /hardware/boards/gerber-bottom.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cheesestraws/airtalk/HEAD/hardware/boards/gerber-bottom.zip -------------------------------------------------------------------------------- /hardware/boards/gerber-mainboard.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cheesestraws/airtalk/HEAD/hardware/boards/gerber-mainboard.zip -------------------------------------------------------------------------------- /main/recovery_listener.h: -------------------------------------------------------------------------------- 1 | #ifndef RECOVERYLISTENER_H 2 | #define RECOVERYLISTENER_H 3 | 4 | void start_recovery_listener(void); 5 | 6 | #endif -------------------------------------------------------------------------------- /hardware/bom/BOM_PCB_airtalk_v2a_2023-02-11.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cheesestraws/airtalk/HEAD/hardware/bom/BOM_PCB_airtalk_v2a_2023-02-11.csv -------------------------------------------------------------------------------- /main/recovery.h: -------------------------------------------------------------------------------- 1 | #ifndef RECOVERY_H 2 | #define RECOVERY_H 3 | 4 | void init_recovery_wifi(void); 5 | void start_recovery_webserver(void); 6 | 7 | #endif -------------------------------------------------------------------------------- /hardware/schematic/Schematic_airtalk_v1_2023-02-11.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cheesestraws/airtalk/HEAD/hardware/schematic/Schematic_airtalk_v1_2023-02-11.pdf -------------------------------------------------------------------------------- /main/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # "main" pseudo-component makefile. 3 | # 4 | # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) 5 | 6 | -------------------------------------------------------------------------------- /main/net_common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | extern esp_netif_t* active_net_if; 8 | extern bool net_if_ready; 9 | 10 | char *generate_hostname(void); -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This is a project Makefile. It is assumed the directory this Makefile resides in is a 3 | # project subdirectory. 4 | # 5 | 6 | PROJECT_NAME := hello-world 7 | 8 | include $(IDF_PATH)/make/project.mk 9 | 10 | -------------------------------------------------------------------------------- /main/wifi.h: -------------------------------------------------------------------------------- 1 | #ifndef WIFI_H 2 | #define WIFI_H 3 | 4 | #include 5 | #include "esp_netif.h" 6 | 7 | void init_at_wifi(void); 8 | esp_netif_t* get_active_net_intf(void); 9 | bool is_net_if_ready(void); 10 | 11 | #endif -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 6 | project(airtalk) -------------------------------------------------------------------------------- /main/scan_manager.h: -------------------------------------------------------------------------------- 1 | #ifndef SCAN_MANAGER_H 2 | #define SCAN_MANAGER_H 3 | 4 | void scan_blocking(void); 5 | void scan_nonblocking(void); 6 | void start_scan_manager(void); 7 | 8 | void scan_and_send_results(uint8_t node, uint8_t socket, uint8_t nbp_id); 9 | 10 | #endif 11 | 12 | -------------------------------------------------------------------------------- /main/pstrings.h: -------------------------------------------------------------------------------- 1 | #ifndef PSTRINGS_H 2 | #define PSTRINGS_H 3 | 4 | #include 5 | 6 | /* pstrings.{c,h} includes utilities to help deal with Pascal strings as 7 | used all over the place in AppleTalk. */ 8 | 9 | char* pstrncpy(char* dst, unsigned char* src, size_t length); 10 | size_t pstrlen(unsigned char* s); 11 | 12 | #endif -------------------------------------------------------------------------------- /main/storage.h: -------------------------------------------------------------------------------- 1 | #ifndef STORAGE_H 2 | #define STORAGE_H 3 | 4 | #include 5 | 6 | void storage_init(); 7 | void store_wifi_details(char* ssid, char* pwd); 8 | void get_wifi_details(char* ssid, size_t ssid_len, char* pwd, size_t pwd_len); 9 | 10 | void store_recovery_for_next_boot(void); 11 | void clear_recovery(void); 12 | bool get_recovery(void); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /main/pstrings.c: -------------------------------------------------------------------------------- 1 | #include "pstrings.h" 2 | 3 | #include 4 | #include 5 | 6 | char* pstrncpy(char* dst, unsigned char* src, size_t length) { 7 | size_t plen = src[0]; 8 | 9 | if (length < plen) { 10 | plen = length; 11 | } 12 | 13 | memmove(dst, src+1, plen); 14 | dst[plen] = '\0'; 15 | 16 | return dst; 17 | } 18 | 19 | size_t pstrlen(unsigned char* s) { 20 | return s[0]; 21 | } 22 | -------------------------------------------------------------------------------- /main/led.h: -------------------------------------------------------------------------------- 1 | #ifndef LED_H 2 | #define LED_H 3 | 4 | typedef enum { 5 | WIFI_RED_LED, WIFI_GREEN_LED, 6 | UDP_RED_LED, UDP_TX_GREEN, UDP_RX_GREEN, 7 | LT_RED_LED, LT_TX_GREEN, LT_RX_GREEN, 8 | OH_NO_LED, ANY_ERR_LED, ANY_ACT_LED, 9 | } LED; 10 | 11 | void led_init(void); 12 | void flash_led_once(LED led); 13 | void flash_all_leds_once(void); 14 | void turn_led_on(LED led); 15 | void turn_led_off(LED led); 16 | void turn_all_leds_on(void); 17 | 18 | #endif -------------------------------------------------------------------------------- /main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(srcs 2 | "buffer_pool.c" 3 | "led.c" 4 | "net_common.c" 5 | "wifi.c" 6 | "ethernet.c" 7 | "ip.c" 8 | "uart.c" 9 | "tashtalk.c" 10 | "crc.c" 11 | "crc32.c" 12 | "node_table.c" 13 | "apps.c" 14 | "scan_manager.c" 15 | "packet_utils.c" 16 | "pstrings.c" 17 | "storage.c" 18 | "recovery_listener.c" 19 | "recovery.c" 20 | "main.c" 21 | ) 22 | 23 | idf_component_register(SRCS "${srcs}" 24 | INCLUDE_DIRS "") -------------------------------------------------------------------------------- /main/net_common.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "net_common.h" 6 | 7 | esp_netif_t* active_net_if = NULL; 8 | bool net_if_ready = false; 9 | 10 | #define BASE_HOSTNAME "airtalk" 11 | 12 | char *generate_hostname(void) { 13 | uint8_t mac[6]; 14 | char *hostname; 15 | esp_read_mac(mac, ESP_MAC_WIFI_STA); 16 | if (-1 == asprintf(&hostname, "%s-%02X%02X%02X", BASE_HOSTNAME, mac[3], mac[4], mac[5])) { 17 | abort(); 18 | } 19 | 20 | return hostname; 21 | } 22 | -------------------------------------------------------------------------------- /main/packet_types.h: -------------------------------------------------------------------------------- 1 | #ifndef PACKET_TYPES_H 2 | #define PACKET_TYPES_H 3 | 4 | #include 5 | 6 | // actually represents an LLAP packet + the FCS 7 | typedef struct { 8 | int length; 9 | unsigned char packet[605]; /* 603 max length of LLAP packet: Inside 10 | Appletalk2nd. Ed. p. 1-6) + 2 bytes FCS. */ 11 | } llap_packet; 12 | 13 | // represents a return address for a LocalTalk NBP LkUp. 14 | typedef struct { 15 | uint8_t node; 16 | uint8_t socket; 17 | uint8_t nbp_id; 18 | } nbp_return_t; 19 | 20 | #endif -------------------------------------------------------------------------------- /main/uart.h: -------------------------------------------------------------------------------- 1 | #ifndef UART_H 2 | #define UART_H 3 | 4 | #include "buffer_pool.h" 5 | #include "node_table.h" 6 | 7 | void uart_init(void); 8 | void uart_init_tashtalk(void); 9 | void uart_start(buffer_pool_t* packet_pool, QueueHandle_t txQueue, QueueHandle_t rxQueue); 10 | void uart_check_for_tx_wedge(llap_packet* packet); 11 | 12 | // get_proxy_nodes returns the node_table for the nodes that we are proxying for 13 | // on the UART side, which is the set of nodes we have seen on the *Wifi* 14 | // side. 15 | node_table_t* get_proxy_nodes(); 16 | 17 | 18 | #endif -------------------------------------------------------------------------------- /main/crc32.h: -------------------------------------------------------------------------------- 1 | #ifndef CRC32_H 2 | #define CRC32_H 3 | 4 | #include 5 | #include 6 | 7 | /* Unlike crc.h, crc32.{h,c} contain an orthodox CRC32. This implementation is 8 | adapted from the one by Gary S. Brown from c.snippets.org, who made it 9 | available for use. The original copyright statement states: 10 | 11 | Copyright (C) 1986 Gary S. Brown. You may use this program, or 12 | code or tables extracted from it, as desired without restriction. 13 | 14 | Gratitude to Gary. */ 15 | 16 | uint32_t crc32buf(char *buf, size_t len); 17 | 18 | #endif -------------------------------------------------------------------------------- /main/ip.h: -------------------------------------------------------------------------------- 1 | #ifndef IP_H 2 | #define IP_H 3 | 4 | #include "buffer_pool.h" 5 | 6 | /* ip.{c,h} contains the code to maintain and listen to the LToUDP socket and 7 | squirt data down it.*/ 8 | 9 | // start_udp starts the UDP connection. Packets that are sent to txQueue are 10 | // transmitted; packets that are received are sent to rxQueue. Packets from 11 | // rxQueue are drawn from the pool; packets in txQueue are returned to the pool 12 | // once sent, so make sure that all packets sent are drawn from the pool. 13 | void start_udp(buffer_pool_t* pool, QueueHandle_t txQueue, QueueHandle_t rxQueue); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /main/apps.h: -------------------------------------------------------------------------------- 1 | #ifndef APPS_H 2 | #define APPS_H 3 | 4 | #include "freertos/FreeRTOS.h" 5 | #include "freertos/queue.h" 6 | 7 | #include "buffer_pool.h" 8 | 9 | /* apps.{c,h} contains the runloop for the "applications"---that is to say, 10 | things that capture and respond to packets from the LocalTalk side. */ 11 | 12 | // start_apps starts the runloop for applications and packet filters 13 | void start_apps(buffer_pool_t* packet_pool, QueueHandle_t fromUART, QueueHandle_t toUART, QueueHandle_t toUDP); 14 | 15 | // send_fake_nbp_LkUpReply does exactly what it says on the tin. 16 | void send_fake_nbp_LkUpReply(uint8_t node, uint8_t socket, uint8_t enumerator, 17 | uint8_t nbp_id, char* object, char* type, char* zone); 18 | 19 | #endif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | 4 | # Libraries 5 | *.lib 6 | *.a 7 | 8 | # Shared objects (inc. Windows DLLs) 9 | *.dll 10 | *.so 11 | *.so.* 12 | *.dylib 13 | 14 | # Executables 15 | *.exe 16 | *.out 17 | *.app 18 | 19 | # http://www.gnu.org/software/automake 20 | Makefile.in 21 | 22 | # http://www.gnu.org/software/autoconf 23 | /autom4te.cache 24 | /aclocal.m4 25 | /compile 26 | /configure 27 | /depcomp 28 | /install-sh 29 | /missing 30 | 31 | # OS X 32 | .DS_Store 33 | .AppleDouble 34 | .LSOverride 35 | Icon 36 | 37 | # Thumbnails 38 | ._* 39 | 40 | # Files that might appear on external disk 41 | .Spotlight-V100 42 | .Trashes 43 | 44 | # Subversion 45 | .svn/ 46 | 47 | # Linux 48 | .* 49 | !.gitignore 50 | *~ 51 | 52 | # Project specific 53 | /build 54 | /main/build 55 | sdkconfig.old 56 | 57 | -------------------------------------------------------------------------------- /main/recovery_listener.c: -------------------------------------------------------------------------------- 1 | #include "driver/gpio.h" 2 | #include "freertos/FreeRTOS.h" 3 | #include "freertos/task.h" 4 | #include "esp_log.h" 5 | 6 | #include "hw.h" 7 | #include "led.h" 8 | #include "storage.h" 9 | 10 | #include "recovery_listener.h" 11 | 12 | void recovery_listener_runloop(void* param) { 13 | int last_recovery = 1; 14 | int recovery; 15 | 16 | while(1) { 17 | // This is a hack but it's simple and unintrusive. 18 | vTaskDelay(800 / portTICK_PERIOD_MS); 19 | 20 | recovery = gpio_get_level(RECOVER_BUTTON); 21 | if (recovery == 0 && last_recovery == 0) { 22 | // start recovery mode 23 | store_recovery_for_next_boot(); 24 | esp_restart(); 25 | } 26 | 27 | last_recovery = recovery; 28 | } 29 | 30 | } 31 | 32 | void start_recovery_listener(void) { 33 | if (RECOVER_BUTTON != -1) { 34 | gpio_reset_pin(RECOVER_BUTTON); 35 | gpio_set_direction(RECOVER_BUTTON, GPIO_MODE_INPUT); 36 | 37 | xTaskCreate(recovery_listener_runloop, "recovery-listener", 2048, NULL, tskIDLE_PRIORITY, NULL); 38 | } 39 | } -------------------------------------------------------------------------------- /main/buffer_pool.h: -------------------------------------------------------------------------------- 1 | #ifndef BUFFER_POOL_H 2 | #define BUFFER_POOL_H 3 | 4 | #include "freertos/FreeRTOS.h" 5 | #include "freertos/queue.h" 6 | 7 | /* This file and its corresponding .c file contain a thread-safe buffer pool 8 | implementation. This is used to avoid having to allocate memory for buffers 9 | dynamically during the lifetime of the application: instead, all buffers can 10 | be allocated at the start of the program, which minimises the likelihood 11 | of nasty memory surprises later. */ 12 | 13 | typedef struct { 14 | int buffer_count; 15 | int buffer_size; 16 | QueueHandle_t queue; 17 | } buffer_pool_t; 18 | 19 | // new_buffer_pool allocates memory for and initialises a new buffer pool. 20 | buffer_pool_t* new_buffer_pool(int buffer_count, int buffer_size); 21 | 22 | // bp_fetch returns a zeroed-out buffer from the pool. 23 | void* bp_fetch(buffer_pool_t* bp); 24 | 25 | // bp_relinquish returns a buffer to the pool. There is no need to zero the 26 | // buffer before relinquishing it. 27 | void bp_relinquish(buffer_pool_t* bp, void** buff); 28 | 29 | int bp_buffersize(buffer_pool_t* bp); 30 | int bp_available(buffer_pool_t* bp); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /main/crc.h: -------------------------------------------------------------------------------- 1 | #ifndef CRC_H 2 | #define CRC_H 3 | 4 | #include 5 | #include 6 | 7 | /* crc.h and crc.c implement the eccentric version of the CRC algorithm as 8 | specified by SDLC and thus LLAP. This implementation is an almost direct 9 | crib from Tashtari's example implementation from tashtalkd. */ 10 | 11 | typedef uint16_t crc_state_t; 12 | 13 | // crc_state_init initialises, *but does not allocate*, a CRC state. Create 14 | // your crc_state_t by another means (e.g. on the stack) first. 15 | void crc_state_init(crc_state_t* state); 16 | 17 | // crc_state_append and crc_state_append_all add either a single character or 18 | // a string to the CRC state, respectively 19 | void crc_state_append(crc_state_t* state, unsigned char b); 20 | void crc_state_append_all(crc_state_t* state, unsigned char* buf, int len); 21 | 22 | // crc_state_ok returns true if the characters appended to the CRC state make 23 | // up a valid LLAP frame 24 | bool crc_state_ok(crc_state_t* state); 25 | 26 | // crc_state_byte_1 and _2 return the two bytes of the two-byte LLAP CRC of the 27 | // payload in the CRC state. 28 | unsigned char crc_state_byte_1(crc_state_t* state); 29 | unsigned char crc_state_byte_2(crc_state_t* state); 30 | 31 | #endif -------------------------------------------------------------------------------- /main/tashtalk.h: -------------------------------------------------------------------------------- 1 | #ifndef TASHTALK_H 2 | #define TASHTALK_H 3 | 4 | #include 5 | 6 | #include "freertos/FreeRTOS.h" 7 | #include "freertos/queue.h" 8 | 9 | #include "buffer_pool.h" 10 | #include "packet_types.h" 11 | #include "crc.h" 12 | 13 | /* This implements the TashTalk *protocol*. UART wrangling is handled in 14 | uart.c instead. */ 15 | 16 | 17 | /* a tashtalk_rx_state_t value represents a state machine for getting LLAP 18 | packets out of the byte stream from TashTalk. */ 19 | typedef struct { 20 | buffer_pool_t* buffer_pool; // the pool to get packet buffers from 21 | llap_packet* packet_in_progress; // the buffer that holds the in-flight packet 22 | QueueHandle_t output_queue; 23 | bool in_escape; 24 | crc_state_t crc; 25 | } tashtalk_rx_state_t; 26 | 27 | tashtalk_rx_state_t* new_tashtalk_rx_state(buffer_pool_t* buffer_pool, 28 | QueueHandle_t output_queue); 29 | void feed(tashtalk_rx_state_t* state, unsigned char byte); 30 | void feed_all(tashtalk_rx_state_t* state, unsigned char* buf, int count); 31 | 32 | // tashtalk_tx_validate checks whether an LLAP packet is valid or whether 33 | // tashtalk will choke on it (i.e. are the lengths right etc) 34 | bool tashtalk_tx_validate(llap_packet* packet); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /hardware/bom/README.md: -------------------------------------------------------------------------------- 1 | # BOM 2 | 3 | The BOM is a snapshot of the parts I used in the last AirTalk production batch before the source release. Nearly every part has a reasonable replacement: no call to stick to it *too* closely. 4 | 5 | Some notes: 6 | 7 | * The BOM does *not* include the mini-DIN sockets. Fortunately the PCB footprints for mini DIN sockets is reasonably standard: my normal practice is to use whatever's decent quality, reasonably cheap, and in stock. 8 | * The BOM does *not* include the TashTalk. In its place it includes an 8-pin DIP socket. On my AirTalks, the TashTalk is socketed. The TashTalk is a PIC12(L)F1840. You can use either the F or LF for AirTalk: I've tended to use the LF just because it's been more reliably in stock at suppliers, but if you have the F version in stock, those'll work too. The firmware for these is at https://github.com/lampmerchant/tashtalk - make sure that you're using a 1.x release of TashTalk, because future releases may change the pinout. 9 | * The RS485 transceivers in the BOM are TP75176E-SRs. There are plenty of alternatives to these. A cheaper alternative that wasn't in stock when I made the BOM is the Gatemode GM3085E, which I tested but which was unavailable for a while. Other transceivers that are known to work are listed at https://github.com/lampmerchant/tashtalk/blob/main/documentation/transceivers.md. Not every RS485 transceiver out there will work, but there are many that will. If in doubt, experiment. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, Rob Mitchelmore 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /hardware/boards/README.md: -------------------------------------------------------------------------------- 1 | # Boards 2 | 3 | There are files for three boards here. Do not be dismayed. The only one that matters, electronically, is the "mainboard". This is the AirTalk itself. The other two boards are just to make a cheapo case. More details on that below. 4 | 5 | The main board has been fabbed successfully at JLCPCB. I haven't tried other fabs. 6 | 7 | 8 | # The "Case" 9 | 10 | The top and bottom PCBs are if you want to make a "case" for it out of two other PCBs. I found this approach gave a really nice result for very little money. Here's how I did this. 11 | 12 | For each AirTalk, you will need: 13 | 14 | * 4 x M3x15mm nylon standoffs (the kind with the internal thread that can take screws in both ends, not with the screw thread sticking out the bottom) 15 | * 4 x M3x5mm nylon standoffs 16 | * 4 x M3x15mm (or M3x12mm) nylon screws 17 | * 4 x M3x5mm nylon screws 18 | 19 | I found that either black hex standoffs + black screws or clear standoffs + clear screws looked best. 20 | 21 | To assemble: 22 | 23 | 1. Take the bottom board. For each of the four screw holes, put a 15mm screw through the hole from the bottom, then put the 5mm standoff onto the screw. Screw it right down until the standoff touches the bottom board. About 10mm of screw thread should be left sticking out the top of the standoff. 24 | 2. Put the mainboard onto the bottom board with the four screw threads through the four holes in the mainboard. 25 | 3. For each of the screw threads, tighten the 15mm standoff onto the bits of screw thread that are visible. This will fix the mainboard down. 26 | 4. Line the four screw holes in the top PCB up with the four 15mm standoffs. Fix the top board down with the four 5mm screws. 27 | 28 | -------------------------------------------------------------------------------- /main/node_table.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "esp_log.h" 4 | 5 | #include "node_table.h" 6 | 7 | void nt_touch(node_table_t* table, uint8_t node) { 8 | if (node == 0 || node == 255) { 9 | return; 10 | } 11 | 12 | table->last_seen[node] = time(NULL); 13 | table->seen_at_all[node] = true; 14 | } 15 | 16 | bool nt_fresh(node_table_t* table, uint8_t node, time_t cutoff) { 17 | time_t now = time(NULL); 18 | 19 | if (!table->seen_at_all[node]) { 20 | return false; 21 | } 22 | 23 | if (now <= cutoff || table->last_seen[node] > (now - cutoff)) { 24 | return true; 25 | } 26 | 27 | return false; 28 | } 29 | 30 | bool nt_serialise(node_table_t* table, node_update_packet_t* packet, time_t cutoff) { 31 | time_t now = time(NULL); 32 | int byte_idx = 0; 33 | bool changed = false; 34 | uint8_t oldbyte; 35 | 36 | // we iterate through the node ID space bytewise 37 | for (int i = 0; i < 256; i += 8) { 38 | // save the old byte so we can check if we changed anything later 39 | oldbyte = packet->nodebits[byte_idx]; 40 | 41 | packet->nodebits[byte_idx] = 0; 42 | for (int j = 0; j < 8; j++) { 43 | // have we seen the node at all? 44 | int node = i + j; 45 | 46 | if (!table->seen_at_all[node]) { 47 | continue; 48 | } 49 | 50 | // we need to explicit check as to whether we've not been up 51 | // longer than cutoff, because our timer starts at 0 when we 52 | // start up (no battery-backed RTC!) 53 | if (now <= cutoff || table->last_seen[node] > (now - cutoff)) { 54 | packet->nodebits[byte_idx] |= (1 << j); 55 | } 56 | } 57 | 58 | if (oldbyte != packet->nodebits[byte_idx]) { 59 | changed = true; 60 | } 61 | byte_idx++; 62 | } 63 | 64 | return changed; 65 | } 66 | -------------------------------------------------------------------------------- /main/node_table.h: -------------------------------------------------------------------------------- 1 | #ifndef NODE_TABLE_H 2 | #define NODE_TABLE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | /* This file, and its corresponding C file, manage a node freshness table. 9 | This table consists of a set of pairs mapping an LocalTalk node ID to 10 | the time it was last seen transmitting. 11 | 12 | We need to know when a node was last transmitting so we can make a reasonable 13 | guess as to whether it's alive or not. If it's alive, we need to tell 14 | TashTalk to proxy LocalTalk control packets (CTS/RTS) for it, because if 15 | we try to tunnel those over UDP, the time guarantees can't be met. (Also, 16 | the LToUDP protocol doesn't let us). 17 | 18 | The node_table_t type maintains this table. */ 19 | typedef struct { 20 | time_t last_seen[256]; 21 | bool seen_at_all[256]; 22 | } node_table_t; 23 | 24 | 25 | /* The node_update_packet_t type contains a serialised form of a node table 26 | to be sent to tashtalk.*/ 27 | typedef struct { 28 | uint8_t nodebits[32]; 29 | } node_update_packet_t; 30 | 31 | 32 | // touch marks a node as alive 33 | void nt_touch(node_table_t* table, uint8_t node); 34 | 35 | // fresh returns true if a node last checked in (using nt_touch) less than 36 | // cutoff seconds ago. 37 | // You can pronounce this "minty fresh" if you want to. 38 | bool nt_fresh(node_table_t* table, uint8_t node, time_t cutoff); 39 | 40 | // serialise turns a node_table_t into a node_update_packet_t, for sending to 41 | // tashtalk. It only includes nodes newer than cutoff seconds old. 42 | // Returns true if changes were made to the node_update_packet_t, or false 43 | // if no changes were made. 44 | bool nt_serialise(node_table_t* table, node_update_packet_t* packet, time_t cutoff); 45 | 46 | 47 | #endif -------------------------------------------------------------------------------- /hardware/README.md: -------------------------------------------------------------------------------- 1 | # Hardware 2 | 3 | **Please note that the hardware is under a different license from the firmware. The firmware is BSD licensed. The hardware is under the CERN-OHL-S-2.0**. 4 | 5 | Now that we've got that out the way: this directory hopefully contains all you need to build your own AirTalk hardware. 6 | 7 | * The schematics are in the schematic/ directory. How imaginative. 8 | * The PCB designs are in the boards/ directory, in both Gerber and EasyEDA format. 9 | * The BoM for the PCB design is in the bom/ directory. 10 | * The pick-and-place file for the PCB design is in the cpl/ directory. 11 | 12 | The whole thing has previously been fabbed and SMD assembled using the JLCPCB service. Other services will probably work just as well. 13 | 14 | As per usual, these come with no guarantee and I can't promise to provide support, because my time is limited. I'll do my best, though. 15 | 16 | 17 | # Using the AirTalk name 18 | 19 | I would request people who build these to follow these guidelines: 20 | 21 | * You can call your AirTalks AirTalks if they use the same firmware, and specifically if they are configured using the AirTalk Chooser extension. If they need to be configured or used in some other way, please call them something else, although you can call them "AirTalk-compatible". This is not about protecting a brand, it's about avoiding confusing users about what software they need. 22 | * If you are making AirTalks for other people or for sale, please put your logo or name on either the board (if bare boards) or case (if cased). This is so that I and other people can see which ones are ones I built and which ones other people have built. 23 | * "Airtalk-compatible" is a perfectly reasonable way to refer to things that use LToUDP, if you want to. 24 | -------------------------------------------------------------------------------- /main/hw.h: -------------------------------------------------------------------------------- 1 | #define REVA 2 | 3 | #ifdef REV0 4 | // REV0 => through-hole prototype 5 | 6 | #define RECOVER_BUTTON GPIO_NUM_34 7 | #define UART_TX GPIO_NUM_14 8 | #define UART_RX GPIO_NUM_27 9 | #define UART_RTS GPIO_NUM_26 10 | #define UART_CTS GPIO_NUM_12 11 | 12 | #define WIFI_RED_LED_PIN 23 13 | #define WIFI_GREEN_LED_PIN 22 14 | #define UDP_RED_LED_PIN 19 15 | #define UDP_TX_GREEN_PIN 18 16 | #define UDP_RX_GREEN_PIN 5 17 | #define LT_RED_LED_PIN 17 18 | #define LT_TX_GREEN_PIN 16 19 | #define LT_RX_GREEN_PIN 4 20 | #define OH_NO_LED_PIN 0 21 | #define GENERIC_ERR_LED_PIN 34 22 | #define GENERIC_ACT_LED_PIN 35 23 | #endif 24 | 25 | #ifdef REVA 26 | // REVA => surface-mount prototype 27 | #define RECOVER_BUTTON GPIO_NUM_0 28 | 29 | #define UART_TX GPIO_NUM_15 30 | #define UART_RX GPIO_NUM_13 31 | #define UART_RTS GPIO_NUM_5 32 | #define UART_CTS GPIO_NUM_2 33 | 34 | #define WIFI_RED_LED_PIN 17 35 | #define WIFI_GREEN_LED_PIN 32 36 | #define UDP_RED_LED_PIN 27 37 | #define UDP_TX_GREEN_PIN 12 38 | #define UDP_RX_GREEN_PIN 14 39 | #define LT_RED_LED_PIN 33 40 | #define LT_TX_GREEN_PIN 25 41 | #define LT_RX_GREEN_PIN 26 42 | #define OH_NO_LED_PIN 18 43 | #define GENERIC_ERR_LED_PIN 5 44 | #define GENERIC_ACT_LED_PIN 16 45 | 46 | #endif 47 | 48 | #ifdef OMNITALK 49 | 50 | #define ETHERNET 51 | #define ETH_MAC_MDC 14 52 | #define ETH_MAC_MDIO 12 53 | #define ETH_50MHZ_EN 33 54 | 55 | #define RECOVER_BUTTON -1 56 | 57 | #define UART_TX GPIO_NUM_2 58 | #define UART_RX GPIO_NUM_15 59 | #define UART_RTS GPIO_NUM_13 60 | #define UART_CTS GPIO_NUM_35 61 | 62 | #define WIFI_RED_LED_PIN -1 63 | #define WIFI_GREEN_LED_PIN 13 64 | #define UDP_RED_LED_PIN -1 65 | #define UDP_TX_GREEN_PIN 4 66 | #define UDP_RX_GREEN_PIN 5 67 | #define LT_RED_LED_PIN -1 68 | #define LT_TX_GREEN_PIN 18 69 | #define LT_RX_GREEN_PIN 23 70 | #define OH_NO_LED_PIN -1 71 | #define GENERIC_ERR_LED_PIN -1 72 | #define GENERIC_ACT_LED_PIN -1 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /main/packet_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef PACKET_UTILS_H 2 | #define PACKET_UTILS_H 3 | 4 | #include 5 | #include 6 | 7 | #include "packet_types.h" 8 | 9 | /* packet_utils.{c,h} include utilities for peering into AppleTalk packets 10 | to work out what they are. */ 11 | 12 | /* LLAP */ 13 | 14 | int llap_type(llap_packet* packet); 15 | int llap_destination_node(llap_packet* packet); 16 | 17 | /* DDP */ 18 | 19 | // is_ddp_packet returns true if an LLAP packet is a DDP packet, false if not. 20 | bool is_ddp_packet(llap_packet* packet); 21 | 22 | // ddp_has_long_headers returns true if the packet is in the long header format 23 | bool ddp_has_long_header(llap_packet* packet); 24 | 25 | // ddp_type returns the ddp type of the packet 26 | uint8_t ddp_type(llap_packet* packet); 27 | 28 | uint8_t ddp_destination_node(llap_packet* packet); 29 | 30 | uint8_t ddp_destination_socket(llap_packet* packet); 31 | 32 | int ddp_payload_offset(llap_packet* packet); 33 | 34 | 35 | /* NBP */ 36 | 37 | #define NBP_LKUP 2 38 | 39 | // an nbp_tuple is a tuple of pointers to *Pascal strings* inside an NBP 40 | // packet. Its lifetime is tied to that of its parent packet. Do not use it 41 | // after its corresponding packet has been relinquished or freed. 42 | typedef struct { 43 | bool ok; 44 | 45 | uint16_t network; 46 | uint8_t node; 47 | uint8_t socket; 48 | uint8_t enumerator; 49 | 50 | unsigned char* object; 51 | unsigned char* type; 52 | unsigned char* zone; 53 | } nbp_tuple_t; 54 | 55 | bool is_nbp_packet(llap_packet* packet); 56 | int nbp_function_code(llap_packet* packet); 57 | int nbp_tuple_count(llap_packet* packet); 58 | int nbp_id(llap_packet* packet); 59 | nbp_tuple_t nbp_tuple(llap_packet* packet, int tuple); 60 | 61 | /* ATP */ 62 | 63 | #define ATP_TREQ 1 64 | #define ATP_TRESP 2 65 | #define ATP_TREL 3 66 | 67 | bool is_atp_packet(llap_packet* packet); 68 | 69 | int atp_function_code(llap_packet* packet); 70 | 71 | int atp_payload_offset(llap_packet* packet); 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /main/ethernet.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "hw.h" 11 | #include "net_common.h" 12 | #include "ethernet.h" 13 | 14 | static const char* TAG = "ETHERNET"; 15 | 16 | 17 | void ethernet_init(void) { 18 | #ifdef ETHERNET 19 | /* set up ESP32 internal MAC */ 20 | eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); 21 | mac_config.sw_reset_timeout_ms = 1000; 22 | 23 | eth_esp32_emac_config_t emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG(); 24 | emac_config.clock_config.rmii.clock_mode = EMAC_CLK_EXT_IN; 25 | emac_config.clock_config.rmii.clock_gpio = EMAC_CLK_IN_GPIO; 26 | emac_config.smi_gpio.mdc_num = ETH_MAC_MDC; 27 | emac_config.smi_gpio.mdio_num = ETH_MAC_MDIO; 28 | 29 | esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&emac_config, &mac_config); 30 | 31 | /* set up PHY */ 32 | eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); 33 | phy_config.phy_addr = 1; 34 | phy_config.reset_gpio_num = -1; 35 | esp_eth_phy_t *phy = esp_eth_phy_new_lan87xx(&phy_config); 36 | 37 | // Enable external oscillator (pulled down at boot to allow IO0 strapping) 38 | ESP_ERROR_CHECK(gpio_set_direction(ETH_50MHZ_EN, GPIO_MODE_OUTPUT)); 39 | ESP_ERROR_CHECK(gpio_set_level(ETH_50MHZ_EN, 1)); 40 | 41 | ESP_LOGD(TAG, "Starting Ethernet interface..."); 42 | 43 | // Install and start Ethernet driver 44 | esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy); 45 | esp_eth_handle_t eth_handle = NULL; 46 | ESP_ERROR_CHECK(esp_eth_driver_install(ð_config, ð_handle)); 47 | 48 | esp_netif_config_t const netif_config = ESP_NETIF_DEFAULT_ETH(); 49 | esp_netif_t *global_netif = esp_netif_new(&netif_config); 50 | esp_eth_netif_glue_handle_t eth_netif_glue = esp_eth_new_netif_glue(eth_handle); 51 | ESP_ERROR_CHECK(esp_netif_attach(global_netif, eth_netif_glue)); 52 | char* hostname = generate_hostname(); 53 | ESP_ERROR_CHECK(esp_netif_set_hostname(global_netif, hostname)); 54 | free(hostname); 55 | ESP_ERROR_CHECK(esp_eth_start(eth_handle)); 56 | 57 | active_net_if = global_netif; 58 | net_if_ready = true; 59 | #endif 60 | } -------------------------------------------------------------------------------- /main/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "sdkconfig.h" 5 | #include "driver/gpio.h" 6 | #include "freertos/FreeRTOS.h" 7 | #include "freertos/task.h" 8 | #include "freertos/queue.h" 9 | #include "esp_system.h" 10 | #include "spi_flash_mmap.h" 11 | #include "esp_event.h" 12 | 13 | #include "hw.h" 14 | 15 | #include "packet_types.h" 16 | #include "buffer_pool.h" 17 | #include "led.h" 18 | #include "wifi.h" 19 | #include "ethernet.h" 20 | #include "ip.h" 21 | #include "uart.h" 22 | #include "apps.h" 23 | #include "scan_manager.h" 24 | #include "storage.h" 25 | #include "recovery.h" 26 | #include "recovery_listener.h" 27 | 28 | void recovery_main(void) { 29 | storage_init(); 30 | led_init(); 31 | turn_all_leds_on(); 32 | 33 | init_recovery_wifi(); 34 | start_recovery_webserver(); 35 | 36 | printf("would enter recovery here"); 37 | while(1) { 38 | vTaskDelay(1000 / portTICK_PERIOD_MS); 39 | } 40 | } 41 | 42 | void app_main(void) 43 | { 44 | printf("storage init\n"); 45 | storage_init(); 46 | uint8_t recovery = 0; 47 | recovery = get_recovery(); 48 | clear_recovery(); 49 | if (recovery) { 50 | recovery_main(); 51 | } 52 | 53 | 54 | // A pool of buffers used for LLAP packets/frames on the way through 55 | buffer_pool_t* packet_pool = new_buffer_pool(180, sizeof(llap_packet)); 56 | 57 | // some queues 58 | QueueHandle_t UARTtoApps = xQueueCreate(60, sizeof(llap_packet*)); 59 | QueueHandle_t AppsToUDP = xQueueCreate(60, sizeof(llap_packet*)); 60 | QueueHandle_t UDPtoUART = xQueueCreate(60, sizeof(llap_packet*)); 61 | 62 | printf("led init\n"); 63 | led_init(); 64 | printf("uart init\n"); 65 | uart_init(); 66 | 67 | ESP_ERROR_CHECK(esp_netif_init()); 68 | ESP_ERROR_CHECK(esp_event_loop_create_default()); 69 | 70 | #ifndef ETHERNET 71 | printf("wifi init\n"); 72 | init_at_wifi(); 73 | #else 74 | printf("ethernet init\n"); 75 | ethernet_init(); 76 | #endif 77 | 78 | start_recovery_listener(); 79 | start_udp(packet_pool, AppsToUDP, UDPtoUART); 80 | start_scan_manager(); 81 | start_apps(packet_pool, UARTtoApps, UDPtoUART, AppsToUDP); 82 | uart_start(packet_pool, UDPtoUART, UARTtoApps); 83 | 84 | while(1) { 85 | vTaskDelay(1000 / portTICK_PERIOD_MS); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /main/html_page.h: -------------------------------------------------------------------------------- 1 | #ifndef HTMLPAGE_H 2 | #define HTMLPAGE_H 3 | 4 | static char* details_page = "AirTalk - Setup Mode
AirTalk Setup

Connect to wireless network:

SSID:
Key:
This will transmit the ssid and key in the query string and it will likely be readable in your browser history. You may wish to avoid using this on shared computers.
"; 5 | 6 | static char* ok_page = "AirTalk - Setup Mode
AirTalk Setup
Writing the details to the AirTalk and resetting...
"; 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /main/wifi.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "freertos/FreeRTOS.h" 5 | #include "freertos/task.h" 6 | #include "freertos/event_groups.h" 7 | #include "esp_system.h" 8 | #include "esp_wifi.h" 9 | #include "esp_event.h" 10 | #include "esp_log.h" 11 | #include "nvs_flash.h" 12 | 13 | #include "lwip/err.h" 14 | #include "lwip/sys.h" 15 | 16 | #include "net_common.h" 17 | #include "led.h" 18 | #include "storage.h" 19 | #include "scan_manager.h" 20 | 21 | static const char* TAG = "APP-WIFI"; 22 | 23 | bool ssid_configured = false; 24 | 25 | static void wifi_event_handler(void* arg, esp_event_base_t event_base, 26 | int32_t event_id, void* event_data) 27 | { 28 | if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { 29 | turn_led_off(WIFI_GREEN_LED); 30 | turn_led_on(WIFI_RED_LED); 31 | scan_blocking(); 32 | net_if_ready = false; 33 | if (ssid_configured) { 34 | esp_wifi_connect(); 35 | } 36 | } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { 37 | turn_led_off(WIFI_GREEN_LED); 38 | turn_led_on(WIFI_RED_LED); 39 | net_if_ready = false; 40 | scan_blocking(); 41 | scan_blocking(); // Do a scan so we have some info when we next connect 42 | esp_wifi_connect(); 43 | } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { 44 | turn_led_off(WIFI_RED_LED); 45 | turn_led_on(WIFI_GREEN_LED); 46 | net_if_ready = true; 47 | } 48 | } 49 | 50 | 51 | void init_at_wifi(void) 52 | { 53 | active_net_if = esp_netif_create_default_wifi_sta(); 54 | 55 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); 56 | ESP_ERROR_CHECK(esp_wifi_init(&cfg)); 57 | 58 | ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL)); 59 | ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL)); 60 | 61 | wifi_config_t wifi_config = {0}; 62 | get_wifi_details((char*)wifi_config.sta.ssid, 32, (char*)wifi_config.sta.password, 64); 63 | ESP_LOGI(TAG,"details from nvs: ssid %s, pwd %s", wifi_config.sta.ssid, wifi_config.sta.password); 64 | // do we have an ssid? 65 | if (wifi_config.sta.ssid[0] != '\0') { 66 | ssid_configured = true; 67 | } 68 | 69 | ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); 70 | ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) ); 71 | ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); 72 | ESP_ERROR_CHECK(esp_wifi_start() ); 73 | 74 | ESP_LOGI(TAG, "wifi_init_sta finished."); 75 | } 76 | 77 | esp_netif_t* get_active_net_intf(void) { 78 | return active_net_if; 79 | } 80 | 81 | bool is_net_if_ready(void) { 82 | return net_if_ready; 83 | } 84 | -------------------------------------------------------------------------------- /main/crc.c: -------------------------------------------------------------------------------- 1 | #include "crc.h" 2 | 3 | /* Look, blame IBM, don't blame me */ 4 | uint16_t LT_CRC_LUT[] = { 5 | 0x0000, 0x1189, 0x2312, 0x329B, 0x4624, 0x57AD, 0x6536, 0x74BF, 0x8C48, 0x9DC1, 0xAF5A, 0xBED3, 0xCA6C, 0xDBE5, 0xE97E, 0xF8F7, 6 | 0x1081, 0x0108, 0x3393, 0x221A, 0x56A5, 0x472C, 0x75B7, 0x643E, 0x9CC9, 0x8D40, 0xBFDB, 0xAE52, 0xDAED, 0xCB64, 0xF9FF, 0xE876, 7 | 0x2102, 0x308B, 0x0210, 0x1399, 0x6726, 0x76AF, 0x4434, 0x55BD, 0xAD4A, 0xBCC3, 0x8E58, 0x9FD1, 0xEB6E, 0xFAE7, 0xC87C, 0xD9F5, 8 | 0x3183, 0x200A, 0x1291, 0x0318, 0x77A7, 0x662E, 0x54B5, 0x453C, 0xBDCB, 0xAC42, 0x9ED9, 0x8F50, 0xFBEF, 0xEA66, 0xD8FD, 0xC974, 9 | 0x4204, 0x538D, 0x6116, 0x709F, 0x0420, 0x15A9, 0x2732, 0x36BB, 0xCE4C, 0xDFC5, 0xED5E, 0xFCD7, 0x8868, 0x99E1, 0xAB7A, 0xBAF3, 10 | 0x5285, 0x430C, 0x7197, 0x601E, 0x14A1, 0x0528, 0x37B3, 0x263A, 0xDECD, 0xCF44, 0xFDDF, 0xEC56, 0x98E9, 0x8960, 0xBBFB, 0xAA72, 11 | 0x6306, 0x728F, 0x4014, 0x519D, 0x2522, 0x34AB, 0x0630, 0x17B9, 0xEF4E, 0xFEC7, 0xCC5C, 0xDDD5, 0xA96A, 0xB8E3, 0x8A78, 0x9BF1, 12 | 0x7387, 0x620E, 0x5095, 0x411C, 0x35A3, 0x242A, 0x16B1, 0x0738, 0xFFCF, 0xEE46, 0xDCDD, 0xCD54, 0xB9EB, 0xA862, 0x9AF9, 0x8B70, 13 | 0x8408, 0x9581, 0xA71A, 0xB693, 0xC22C, 0xD3A5, 0xE13E, 0xF0B7, 0x0840, 0x19C9, 0x2B52, 0x3ADB, 0x4E64, 0x5FED, 0x6D76, 0x7CFF, 14 | 0x9489, 0x8500, 0xB79B, 0xA612, 0xD2AD, 0xC324, 0xF1BF, 0xE036, 0x18C1, 0x0948, 0x3BD3, 0x2A5A, 0x5EE5, 0x4F6C, 0x7DF7, 0x6C7E, 15 | 0xA50A, 0xB483, 0x8618, 0x9791, 0xE32E, 0xF2A7, 0xC03C, 0xD1B5, 0x2942, 0x38CB, 0x0A50, 0x1BD9, 0x6F66, 0x7EEF, 0x4C74, 0x5DFD, 16 | 0xB58B, 0xA402, 0x9699, 0x8710, 0xF3AF, 0xE226, 0xD0BD, 0xC134, 0x39C3, 0x284A, 0x1AD1, 0x0B58, 0x7FE7, 0x6E6E, 0x5CF5, 0x4D7C, 17 | 0xC60C, 0xD785, 0xE51E, 0xF497, 0x8028, 0x91A1, 0xA33A, 0xB2B3, 0x4A44, 0x5BCD, 0x6956, 0x78DF, 0x0C60, 0x1DE9, 0x2F72, 0x3EFB, 18 | 0xD68D, 0xC704, 0xF59F, 0xE416, 0x90A9, 0x8120, 0xB3BB, 0xA232, 0x5AC5, 0x4B4C, 0x79D7, 0x685E, 0x1CE1, 0x0D68, 0x3FF3, 0x2E7A, 19 | 0xE70E, 0xF687, 0xC41C, 0xD595, 0xA12A, 0xB0A3, 0x8238, 0x93B1, 0x6B46, 0x7ACF, 0x4854, 0x59DD, 0x2D62, 0x3CEB, 0x0E70, 0x1FF9, 20 | 0xF78F, 0xE606, 0xD49D, 0xC514, 0xB1AB, 0xA022, 0x92B9, 0x8330, 0x7BC7, 0x6A4E, 0x58D5, 0x495C, 0x3DE3, 0x2C6A, 0x1EF1, 0x0F78, 21 | }; 22 | 23 | void crc_state_init(crc_state_t* state) { 24 | *state = 0xFFFF; 25 | } 26 | 27 | void crc_state_append(crc_state_t* state, unsigned char b) { 28 | int index = (*state & 0xFF) ^ b; 29 | *state = LT_CRC_LUT[index] ^ (*state >> 8); 30 | } 31 | 32 | void crc_state_append_all(crc_state_t* state, unsigned char* buf, int len) { 33 | for (int i = 0; i < len; i++) { 34 | crc_state_append(state, buf[i]); 35 | } 36 | } 37 | 38 | bool crc_state_ok(crc_state_t* state) { 39 | return *state == 61624; 40 | } 41 | 42 | unsigned char crc_state_byte_1(crc_state_t* state) { 43 | return (*state & 0xFF) ^ 0xFF; 44 | } 45 | 46 | unsigned char crc_state_byte_2(crc_state_t* state) { 47 | return (*state >> 8) ^ 0xFF; 48 | } 49 | -------------------------------------------------------------------------------- /main/storage.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "storage.h" 4 | 5 | #include "esp_system.h" 6 | #include "esp_log.h" 7 | #include "nvs_flash.h" 8 | #include "nvs.h" 9 | 10 | #include "led.h" 11 | 12 | static const char* TAG = "storage"; 13 | 14 | void storage_init() { 15 | esp_err_t err = nvs_flash_init(); 16 | if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { 17 | ESP_ERROR_CHECK(nvs_flash_erase()); 18 | err = nvs_flash_init(); 19 | } 20 | if (err) { 21 | turn_led_on(OH_NO_LED); 22 | ESP_LOGE(TAG, "could not init NVS!"); 23 | } 24 | } 25 | 26 | void store_wifi_details(char* ssid, char* pwd) { 27 | esp_err_t err; 28 | nvs_handle_t h; 29 | 30 | err = nvs_open("wifi", NVS_READWRITE, &h); 31 | if (err != ESP_OK) { 32 | ESP_LOGE(TAG, "couldn't write NVS: %s", esp_err_to_name(err)); 33 | turn_led_on(OH_NO_LED); 34 | return; 35 | } 36 | 37 | nvs_set_str(h, "ssid", ssid); 38 | nvs_set_str(h, "pwd", pwd); 39 | 40 | nvs_commit(h); 41 | nvs_close(h); 42 | } 43 | 44 | void get_wifi_details(char* ssid, size_t ssid_len, char* pwd, size_t pwd_len) { 45 | esp_err_t err; 46 | nvs_handle_t h; 47 | 48 | err = nvs_open("wifi", NVS_READWRITE, &h); 49 | if (err != ESP_OK) { 50 | ESP_LOGE(TAG, "couldn't write NVS: %s", esp_err_to_name(err)); 51 | turn_led_on(OH_NO_LED); 52 | return; 53 | } 54 | 55 | nvs_get_str(h, "ssid", ssid, &ssid_len); 56 | nvs_get_str(h, "pwd", pwd, &pwd_len); 57 | 58 | nvs_close(h); 59 | } 60 | 61 | void store_recovery_for_next_boot(void) { 62 | esp_err_t err; 63 | nvs_handle_t h; 64 | 65 | err = nvs_open("airtalk", NVS_READWRITE, &h); 66 | if (err != ESP_OK) { 67 | ESP_LOGE(TAG, "couldn't write NVS: %s", esp_err_to_name(err)); 68 | turn_led_on(OH_NO_LED); 69 | return; 70 | } 71 | 72 | err = nvs_set_u8(h, "recovery", 1); 73 | if (err != ESP_OK) { 74 | ESP_LOGE(TAG, "couldn't write NVS: %s", esp_err_to_name(err)); 75 | turn_led_on(OH_NO_LED); 76 | return; 77 | } 78 | 79 | nvs_commit(h); 80 | nvs_close(h); 81 | } 82 | 83 | void clear_recovery(void) { 84 | esp_err_t err; 85 | nvs_handle_t h; 86 | 87 | 88 | err = nvs_open("airtalk", NVS_READWRITE, &h); 89 | if (err != ESP_OK) { 90 | ESP_LOGE(TAG, "couldn't write NVS: %s", esp_err_to_name(err)); 91 | turn_led_on(OH_NO_LED); 92 | return; 93 | } 94 | 95 | err = nvs_set_u8(h, "recovery", 0); 96 | 97 | nvs_commit(h); 98 | nvs_close(h); 99 | } 100 | 101 | bool get_recovery(void) { 102 | esp_err_t err; 103 | nvs_handle_t h; 104 | uint8_t recovery = 0; 105 | 106 | err = nvs_open("airtalk", NVS_READWRITE, &h); 107 | if (err != ESP_OK) { 108 | ESP_LOGE(TAG, "couldn't write NVS: %s", esp_err_to_name(err)); 109 | turn_led_on(OH_NO_LED); 110 | return false; 111 | } 112 | 113 | nvs_get_u8(h, "recovery", &recovery); 114 | 115 | nvs_close(h); 116 | return recovery == 1; 117 | } 118 | -------------------------------------------------------------------------------- /main/buffer_pool.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "esp_log.h" 4 | 5 | #include "buffer_pool.h" 6 | #include "led.h" 7 | 8 | static const char* TAG = "bufferpool"; 9 | 10 | /* the buffer_pool_t type is defined in the header file */ 11 | 12 | void bp_panic() { 13 | ESP_LOGE(TAG, "PANIC AT THE DISCO! PANIC IN THE BUFFER POOL!"); 14 | turn_led_on(OH_NO_LED); 15 | } 16 | 17 | 18 | buffer_pool_t* new_buffer_pool(int buffer_count, int buffer_size) { 19 | QueueHandle_t queue; 20 | 21 | // Note that the items we store in the queue are *pointers* to the buffers 22 | // not the buffers themselves. So 23 | queue = xQueueCreate(buffer_count, sizeof(char*)); 24 | 25 | if (queue==NULL) { 26 | goto handle_err; 27 | } 28 | 29 | // Now create the buffers 30 | for (int i = 0; i < buffer_count; i++) { 31 | char* buffer; 32 | buffer = malloc(buffer_size); 33 | if (buffer == NULL) { 34 | goto handle_err; 35 | } 36 | 37 | bzero(buffer, buffer_size); 38 | 39 | BaseType_t result = xQueueSendToBack(queue, &buffer, 0); 40 | if (result == errQUEUE_FULL) { 41 | ESP_LOGE(TAG, "BUG: in new_buffer_pool, why is the queue full?"); 42 | turn_led_on(OH_NO_LED); 43 | free(buffer); 44 | } 45 | } 46 | 47 | // Now construct the struct to return 48 | buffer_pool_t* ret = malloc(sizeof(buffer_pool_t)); 49 | if (ret == NULL) { 50 | goto handle_err; 51 | } 52 | 53 | ret->buffer_count = buffer_count; 54 | ret->buffer_size = buffer_size; 55 | ret->queue = queue; 56 | return ret; 57 | 58 | handle_err: 59 | /* TODO: error handling */ 60 | bp_panic(); 61 | return NULL; 62 | } 63 | 64 | void* bp_fetch(buffer_pool_t* bp) { 65 | if (bp == NULL || bp->queue == NULL) { 66 | ESP_LOGE(TAG, "BUG: bp_fetch called on null pool or pool with null queue"); 67 | turn_led_on(OH_NO_LED); 68 | return NULL; 69 | } 70 | 71 | void* buffer; 72 | BaseType_t ret = xQueueReceive(bp->queue, &buffer, 0); 73 | 74 | if (ret == pdTRUE) { 75 | return buffer; 76 | } else { 77 | ESP_LOGW(TAG, "BUG: bp_fetch underflowed buffer pool"); 78 | turn_led_on(OH_NO_LED); 79 | return NULL; 80 | } 81 | } 82 | 83 | void bp_relinquish(buffer_pool_t* bp, void** buff) { 84 | if (buff == NULL) { 85 | ESP_LOGE(TAG, "BUG: bp_relinquish called on null pointer"); 86 | turn_led_on(OH_NO_LED); 87 | } 88 | if (*buff == NULL) { 89 | ESP_LOGE(TAG, "BUG: bp_relinquish called on pointer to null buffer"); 90 | turn_led_on(OH_NO_LED); 91 | } 92 | 93 | // Try to push the buffer to the queue 94 | BaseType_t result = xQueueSendToBack(bp->queue, buff, 0); 95 | if (result == errQUEUE_FULL) { 96 | ESP_LOGE(TAG, "BUG: bp_relinquish queue overflow; have you relinquished a bad pointer?"); 97 | turn_led_on(OH_NO_LED); 98 | return; 99 | } 100 | 101 | bzero(*buff, bp->buffer_size); 102 | 103 | *buff = NULL; 104 | } 105 | 106 | int bp_buffersize(buffer_pool_t* bp) { 107 | return bp->buffer_size; 108 | } 109 | -------------------------------------------------------------------------------- /main/scan_manager.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "esp_err.h" 7 | #include "esp_wifi.h" 8 | #include "esp_log.h" 9 | #include "freertos/task.h" 10 | #include "freertos/semphr.h" 11 | 12 | #include "led.h" 13 | #include "apps.h" 14 | 15 | static const char* TAG = "SCANMAN"; 16 | 17 | TaskHandle_t scanner_task; 18 | 19 | struct { 20 | SemaphoreHandle_t lock; 21 | uint16_t ap_count; 22 | wifi_ap_record_t* aps; 23 | time_t when; 24 | } scan_state = { 0 }; 25 | 26 | void scan_blocking(void) { 27 | esp_err_t err; 28 | 29 | err = esp_wifi_scan_start(NULL, true); 30 | if (err == ESP_ERR_WIFI_STATE) { 31 | // if we're connecting, nowt we can do about it. 32 | return; 33 | } 34 | 35 | if (err != ESP_OK) { 36 | ESP_LOGE(TAG, "error scanning: %s", esp_err_to_name(err)); 37 | return; 38 | } 39 | 40 | // get the AP information 41 | uint16_t ap_count; 42 | ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(&ap_count)); 43 | // should probably clamp ap_count to some max but meh 44 | 45 | if (ap_count == 0) { 46 | return; 47 | } 48 | 49 | wifi_ap_record_t* aps; 50 | aps = malloc(ap_count * sizeof(wifi_ap_record_t)); 51 | if (aps == NULL) { 52 | turn_led_on(OH_NO_LED); 53 | ESP_LOGE(TAG, "malloc() failed when allocating %d aps!", ap_count); 54 | return; 55 | } 56 | esp_wifi_scan_get_ap_records(&ap_count, aps); 57 | 58 | for (int i = 0; i < ap_count; i++) { 59 | ESP_LOGI(TAG, "Found AP: %s", aps[i].ssid); 60 | } 61 | 62 | // Now dump them into the scan state, taking the mutex to avoid crashes 63 | xSemaphoreTake(scan_state.lock, portMAX_DELAY); 64 | 65 | // is the old AP state initialised? 66 | if (scan_state.aps != NULL) { 67 | free(scan_state.aps); 68 | } 69 | 70 | scan_state.ap_count = ap_count; 71 | scan_state.aps = aps; 72 | scan_state.when = time(NULL); 73 | 74 | xSemaphoreGive(scan_state.lock); 75 | 76 | // do NOT free aps, since we've stashed it in scan_state 77 | } 78 | 79 | void scan_nonblocking(void) { 80 | xTaskNotify(scanner_task, 1, eSetValueWithOverwrite); 81 | } 82 | 83 | void scan_manager_runloop(void* param) { 84 | uint32_t dummy_value = 0; 85 | 86 | while(1) { 87 | xTaskNotifyWait(0, 0, &dummy_value, portMAX_DELAY); 88 | scan_blocking(); 89 | vTaskDelay(1000 / portTICK_PERIOD_MS); 90 | } 91 | } 92 | 93 | void start_scan_manager(void) { 94 | scan_state.lock = xSemaphoreCreateMutex(); 95 | 96 | xTaskCreate(scan_manager_runloop, "SCANMAN", 4096, NULL, tskIDLE_PRIORITY, &scanner_task); 97 | } 98 | 99 | void scan_and_send_results(uint8_t node, uint8_t socket, uint8_t nbp_id) { 100 | xSemaphoreTake(scan_state.lock, portMAX_DELAY); 101 | 102 | time_t now = time(NULL); 103 | 104 | // Are the results young enough to send? 105 | if (scan_state.aps != NULL && scan_state.when > (now - 30)) { 106 | for (int i = 0; i < scan_state.ap_count; i++) { 107 | // we can't deal with ssids longer than 32 characters (?) 108 | if (strlen((char*)scan_state.aps[i].ssid) > 32) { 109 | continue; 110 | } 111 | 112 | send_fake_nbp_LkUpReply(node, socket, i, nbp_id, 113 | (char*)scan_state.aps[i].ssid, "AirTalkAP", "*"); 114 | } 115 | } 116 | 117 | scan_nonblocking(); 118 | 119 | xSemaphoreGive(scan_state.lock); 120 | } 121 | -------------------------------------------------------------------------------- /main/crc32.c: -------------------------------------------------------------------------------- 1 | #include "crc32.h" 2 | 3 | /* Derived from code bearing the following copyright statement: 4 | 5 | Copyright (C) 1986 Gary S. Brown. You may use this program, or 6 | code or tables extracted from it, as desired without restriction. */ 7 | 8 | static uint32_t crc_32_tab[] = { /* CRC polynomial 0xedb88320 */ 9 | 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 10 | 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 11 | 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 12 | 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 13 | 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 14 | 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 15 | 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 16 | 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 17 | 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 18 | 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 19 | 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 20 | 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 21 | 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 22 | 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 23 | 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 24 | 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 25 | 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 26 | 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 27 | 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 28 | 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 29 | 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 30 | 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 31 | 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 32 | 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 33 | 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 34 | 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 35 | 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 36 | 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 37 | 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 38 | 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 39 | 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 40 | 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 41 | 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 42 | 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 43 | 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 44 | 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 45 | 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 46 | 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 47 | 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 48 | 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 49 | 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 50 | 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 51 | 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d 52 | }; 53 | 54 | #define UPDC32(octet,crc) (crc_32_tab[((crc)\ 55 | ^ ((uint8_t)octet)) & 0xff] ^ ((crc) >> 8)) 56 | 57 | 58 | uint32_t crc32buf(char *buf, size_t len) 59 | { 60 | register uint32_t oldcrc32; 61 | 62 | oldcrc32 = 0xFFFFFFFF; 63 | 64 | for ( ; len; --len, ++buf) 65 | { 66 | oldcrc32 = UPDC32(*buf, oldcrc32); 67 | } 68 | 69 | return ~oldcrc32; 70 | } 71 | -------------------------------------------------------------------------------- /main/recovery.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "freertos/FreeRTOS.h" 7 | #include "freertos/task.h" 8 | #include "freertos/event_groups.h" 9 | #include "esp_system.h" 10 | #include "esp_wifi.h" 11 | #include "esp_event.h" 12 | #include "esp_netif.h" 13 | #include "esp_log.h" 14 | #include "esp_http_server.h" 15 | #include "nvs_flash.h" 16 | #include "lwip/err.h" 17 | #include "lwip/sys.h" 18 | 19 | #include "recovery.h" 20 | #include "storage.h" 21 | #include "html_page.h" 22 | 23 | const static char* TAG = "recovery-app"; 24 | 25 | static esp_netif_t *ap_if; 26 | static httpd_handle_t srv; 27 | 28 | #include 29 | #include 30 | 31 | void urlndecode(char *dst, const char *src, size_t len) 32 | { 33 | char a, b; 34 | size_t cnt = 0; 35 | while (*src) { 36 | if ((*src == '%') && 37 | ((a = src[1]) && (b = src[2])) && 38 | (isxdigit(a) && isxdigit(b))) { 39 | if (a >= 'a') 40 | a -= 'a'-'A'; 41 | if (a >= 'A') 42 | a -= ('A' - 10); 43 | else 44 | a -= '0'; 45 | if (b >= 'a') 46 | b -= 'a'-'A'; 47 | if (b >= 'A') 48 | b -= ('A' - 10); 49 | else 50 | b -= '0'; 51 | *dst++ = 16*a+b; 52 | src+=3; 53 | } else if (*src == '+') { 54 | *dst++ = ' '; 55 | src++; 56 | } else { 57 | *dst++ = *src++; 58 | } 59 | cnt++; 60 | if (cnt >= (len - 1)) { 61 | break; 62 | } 63 | } 64 | *dst++ = '\0'; 65 | } 66 | 67 | void init_recovery_wifi(void) { 68 | char myssid[33] = {0}; 69 | uint8_t mymac[6] = {0}; 70 | 71 | ESP_ERROR_CHECK(esp_netif_init()); 72 | ESP_ERROR_CHECK(esp_event_loop_create_default()); 73 | ap_if = esp_netif_create_default_wifi_ap(); 74 | 75 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); 76 | ESP_ERROR_CHECK(esp_wifi_init(&cfg)); 77 | 78 | ESP_ERROR_CHECK(esp_wifi_get_mac(ESP_IF_WIFI_AP, mymac)); 79 | sprintf(myssid, "AirTalk-%02x%02x%02x%02x%02x%02x", mymac[0], mymac[1], mymac[2], 80 | mymac[3], mymac[4], mymac[5]); 81 | 82 | ESP_LOGI(TAG, "starting recovery mode on SSID %s", myssid); 83 | 84 | 85 | wifi_config_t wifi_config = { 86 | .ap = { 87 | .ssid_len = strlen(myssid), 88 | .channel = 1, 89 | .password = "airsetup", 90 | .max_connection = 2, 91 | .authmode = WIFI_AUTH_WPA_WPA2_PSK 92 | }, 93 | }; 94 | 95 | // not an off-by-one: myssid is null-terminated, but the ssid field in the 96 | // config has an explicitly set length and no null terminator. 97 | memcpy(wifi_config.ap.ssid, myssid, 32); 98 | 99 | ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); 100 | ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config)); 101 | ESP_ERROR_CHECK(esp_wifi_start()); 102 | 103 | } 104 | 105 | static esp_err_t root_handler(httpd_req_t *req) { 106 | char* qry; 107 | // urlencoded ssid and key might be up to 3x the length of 'real' 108 | // ssid and key if every character is %-encoded. (Note the last char 109 | // of each is a null, so we don't actually have 3* number of characters) 110 | bool got_ssid = false; 111 | char ssid[33] = {0}; 112 | char encoded_ssid[97] = {0}; 113 | bool got_key = false; 114 | char key[65] = {0}; 115 | char encoded_key[193] = {0}; 116 | 117 | // Do we have a query string? 118 | size_t buf_len = httpd_req_get_url_query_len(req) + 1; 119 | if (buf_len > 1) { 120 | qry = malloc(buf_len); 121 | if (httpd_req_get_url_query_str(req, qry, buf_len) == ESP_OK) { 122 | if (httpd_query_key_value(qry, "ssid", encoded_ssid, sizeof(encoded_ssid)) == ESP_OK) { 123 | urlndecode(ssid, encoded_ssid, sizeof(ssid)); 124 | got_ssid = true; 125 | ESP_LOGI(TAG, "setting ssid => %s", ssid); 126 | } 127 | 128 | if (httpd_query_key_value(qry, "key", encoded_key, sizeof(encoded_key)) == ESP_OK) { 129 | urlndecode(key, encoded_key, sizeof(key)); 130 | got_key = true; 131 | ESP_LOGI(TAG, "setting key => %s", key); 132 | } 133 | } 134 | free(qry); 135 | } 136 | 137 | if (got_ssid && got_key) { 138 | httpd_resp_send(req, ok_page, HTTPD_RESP_USE_STRLEN); 139 | ESP_LOGI(TAG, "would save (%s, %s) and reboot here", ssid, key); 140 | store_wifi_details(ssid, key); 141 | esp_restart(); 142 | } else { 143 | httpd_resp_send(req, details_page, HTTPD_RESP_USE_STRLEN); 144 | } 145 | return ESP_OK; 146 | } 147 | 148 | static const httpd_uri_t root = { 149 | .uri = "/", 150 | .method = HTTP_GET, 151 | .handler = root_handler, 152 | }; 153 | 154 | 155 | void start_recovery_webserver(void) { 156 | httpd_config_t config = HTTPD_DEFAULT_CONFIG(); 157 | ESP_ERROR_CHECK(httpd_start(&srv, &config)); 158 | httpd_register_uri_handler(srv, &root); 159 | } -------------------------------------------------------------------------------- /main/uart.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "freertos/FreeRTOS.h" 5 | #include "freertos/task.h" 6 | #include "esp_system.h" 7 | #include "esp_log.h" 8 | #include "driver/uart.h" 9 | #include "driver/gpio.h" 10 | 11 | #include "led.h" 12 | #include "tashtalk.h" 13 | #include "node_table.h" 14 | #include "packet_types.h" 15 | #include "packet_utils.h" 16 | #include "hw.h" 17 | 18 | #include "uart.h" 19 | 20 | static const char* TAG = "uart"; 21 | static const int uart_num = UART_NUM_1; 22 | 23 | node_table_t node_table = { 0 }; 24 | node_update_packet_t node_table_packet = { 0 }; 25 | 26 | TaskHandle_t uart_rx_task = NULL; 27 | TaskHandle_t uart_tx_task = NULL; 28 | QueueHandle_t uart_rx_queue = NULL; 29 | QueueHandle_t uart_tx_queue = NULL; 30 | 31 | 32 | 33 | #define RX_BUFFER_SIZE 1024 34 | 35 | uint8_t uart_buffer[RX_BUFFER_SIZE]; 36 | 37 | 38 | void uart_write_node_table(int uart_num, node_update_packet_t* packet) { 39 | uart_write_bytes(uart_num, "\x02", 1); 40 | uart_write_bytes(uart_num, (char*)packet->nodebits, 32); 41 | 42 | // debugomatic 43 | char debugbuff[97] = {0}; 44 | char* ptr = debugbuff; 45 | for (int i = 0; i < 32; i++) { 46 | ptr += sprintf(ptr, "%02X ", packet->nodebits[i]); 47 | } 48 | ESP_LOGI(TAG, "sent nodebits: %s", debugbuff); 49 | } 50 | 51 | node_table_t* get_proxy_nodes() { 52 | return &node_table; 53 | } 54 | 55 | void uart_init_tashtalk(void) { 56 | char init[1024] = { 0 }; 57 | uart_write_bytes(uart_num, init, 1024); 58 | uart_write_node_table(uart_num, &node_table_packet); 59 | } 60 | 61 | void uart_check_for_tx_wedge(llap_packet* packet) { 62 | // Sometimes, if buffer weirdness happens, tashtalk gets wedged. In those 63 | // cases, we need to detect it. We can detect this by looking for strings 64 | // of handshaking requests. 65 | // 66 | // This works because we know that we should respond to a CTS for one of our 67 | // nodes. If we get more than one burst of CTSes, something's gone wrong 68 | // and we should reset the tashtalk to a known state. 69 | 70 | static int handshake_count = 0; 71 | if (llap_type(packet) == 0x84 || llap_type(packet) == 0x85) { 72 | handshake_count++; 73 | 74 | if (handshake_count > 10) { 75 | flash_led_once(LT_RED_LED); 76 | ESP_LOGE(TAG, "%d consecutive handshakes! reinitialising tashtalk", handshake_count); 77 | uart_init_tashtalk(); 78 | handshake_count = 0; 79 | } 80 | } else { 81 | handshake_count = 0; 82 | } 83 | } 84 | 85 | void uart_init(void) { 86 | const uart_config_t uart_config = { 87 | .baud_rate = 1000000, 88 | .data_bits = UART_DATA_8_BITS, 89 | .parity = UART_PARITY_DISABLE, 90 | .stop_bits = UART_STOP_BITS_1, 91 | .flow_ctrl = UART_HW_FLOWCTRL_CTS_RTS, 92 | .rx_flow_ctrl_thresh = 0, 93 | }; 94 | 95 | ESP_ERROR_CHECK(uart_param_config(uart_num, &uart_config)); 96 | ESP_ERROR_CHECK(uart_set_pin(uart_num, UART_TX, UART_RX, UART_RTS, UART_CTS)); 97 | ESP_ERROR_CHECK(uart_driver_install(uart_num, 2048, 0, 0, NULL, 0)); 98 | 99 | uart_init_tashtalk(); 100 | 101 | ESP_LOGI(TAG, "uart_init complete"); 102 | } 103 | 104 | void uart_rx_runloop(void* buffer_pool) { 105 | static const char *TAG = "UART_RX"; 106 | ESP_LOGI(TAG, "started"); 107 | 108 | tashtalk_rx_state_t* rxstate = new_tashtalk_rx_state((buffer_pool_t*)buffer_pool, uart_rx_queue); 109 | 110 | while(1){ 111 | /* TODO: this is stupid, read a byte at a time instead and wait for MAX_DELAY */ 112 | const int len = uart_read_bytes(uart_num, uart_buffer, 1, 1000 / portTICK_PERIOD_MS); 113 | if (len > 0) { 114 | feed_all(rxstate, uart_buffer, len); 115 | } 116 | } 117 | } 118 | 119 | void uart_tx_runloop(void* buffer_pool) { 120 | llap_packet* packet = NULL; 121 | static const char *TAG = "UART_TX"; 122 | 123 | ESP_LOGI(TAG, "started"); 124 | 125 | while(1){ 126 | /* TODO: we should time out here every so often to re-calculate stale 127 | nodes in the node table */ 128 | xQueueReceive(uart_tx_queue, &packet, portMAX_DELAY); 129 | 130 | // validate the packet: first, check the CRC: 131 | crc_state_t crc; 132 | crc_state_init(&crc); 133 | crc_state_append_all(&crc, packet->packet, packet->length); 134 | 135 | if (!tashtalk_tx_validate(packet)) { 136 | ESP_LOGE(TAG, "packet validation failed"); 137 | flash_led_once(LT_RED_LED); 138 | goto skip_processing; 139 | } 140 | 141 | // Now mark the sender as active and see if we need to update tashtalk's 142 | // node mask 143 | nt_touch(&node_table, packet->packet[1]); 144 | bool changed = nt_serialise(&node_table, &node_table_packet, 1800); 145 | if (changed) { 146 | ESP_LOGI(TAG, "node table changed!"); 147 | uart_write_node_table(uart_num, &node_table_packet); 148 | } 149 | 150 | uart_write_bytes(uart_num, "\x01", 1); 151 | uart_write_bytes(uart_num, (const char*)packet->packet, packet->length); 152 | 153 | flash_led_once(LT_TX_GREEN); 154 | 155 | skip_processing: 156 | bp_relinquish((buffer_pool_t*)buffer_pool, (void**)&packet); 157 | } 158 | } 159 | 160 | void uart_start(buffer_pool_t* packet_pool, QueueHandle_t txQueue, QueueHandle_t rxQueue) { 161 | uart_rx_queue = rxQueue; 162 | uart_tx_queue = txQueue; 163 | xTaskCreate(&uart_rx_runloop, "UART_RX", 4096, (void*)packet_pool, 5, &uart_rx_task); 164 | xTaskCreate(&uart_tx_runloop, "UART_TX", 4096, (void*)packet_pool, 5, &uart_tx_task); 165 | } 166 | -------------------------------------------------------------------------------- /main/led.c: -------------------------------------------------------------------------------- 1 | #include "driver/gpio.h" 2 | #include "freertos/FreeRTOS.h" 3 | #include "freertos/task.h" 4 | #include "esp_log.h" 5 | 6 | #include "hw.h" 7 | 8 | #include "led.h" 9 | 10 | #define FLASH_LENGTH 125 11 | 12 | static const char* TAG = "LED"; 13 | void led_runloop(void* param); 14 | 15 | /* Configuration for the status LEDs */ 16 | typedef struct { 17 | /* initialise these fields */ 18 | 19 | int enabled; 20 | char* name; 21 | int gpio_pin; 22 | int also; 23 | 24 | /* don't initialise these */ 25 | 26 | TaskHandle_t task; 27 | int gpio_state; 28 | } led_config_t; 29 | 30 | static led_config_t led_config[] = { 31 | [WIFI_RED_LED] = { 32 | .enabled = 1, 33 | .name = "WIFI_RED_LED", 34 | .gpio_pin = WIFI_RED_LED_PIN 35 | }, 36 | 37 | [WIFI_GREEN_LED] = { 38 | .enabled = 1, 39 | .name = "WIFI_GREEN_LED", 40 | .gpio_pin = WIFI_GREEN_LED_PIN 41 | }, 42 | 43 | [UDP_RED_LED] = { 44 | .enabled = 1, 45 | .name = "UDP_RED_LED", 46 | .gpio_pin = UDP_RED_LED_PIN, 47 | .also = ANY_ERR_LED 48 | }, 49 | 50 | [UDP_TX_GREEN] = { 51 | .enabled = 1, 52 | .name = "UDP_TX_GREEN", 53 | .gpio_pin = UDP_TX_GREEN_PIN, 54 | .also = ANY_ACT_LED 55 | }, 56 | 57 | [UDP_RX_GREEN] = { 58 | .enabled = 1, 59 | .name = "UDP_RX_GREEN", 60 | .gpio_pin = UDP_RX_GREEN_PIN, 61 | .also = ANY_ACT_LED 62 | }, 63 | 64 | [LT_RED_LED] = { 65 | .enabled = 1, 66 | .name = "LT_RED_LED", 67 | .gpio_pin = LT_RED_LED_PIN, 68 | .also = ANY_ERR_LED 69 | }, 70 | 71 | [LT_TX_GREEN] = { 72 | .enabled = 1, 73 | .name = "LT_TX_GREEN", 74 | .gpio_pin = LT_TX_GREEN_PIN, 75 | .also = ANY_ACT_LED 76 | }, 77 | 78 | [LT_RX_GREEN] = { 79 | .enabled = 1, 80 | .name = "LT_RX_GREEN", 81 | .gpio_pin = LT_RX_GREEN_PIN, 82 | .also = ANY_ACT_LED 83 | }, 84 | 85 | [OH_NO_LED] = { 86 | .enabled = 1, 87 | .name = "OH_NO_LED", 88 | .gpio_pin = OH_NO_LED_PIN 89 | }, 90 | 91 | [ANY_ERR_LED] = { 92 | .enabled = 1, 93 | .name = "ANY_ERR_LED", 94 | .gpio_pin = GENERIC_ERR_LED_PIN 95 | }, 96 | 97 | [ANY_ACT_LED] = { 98 | .enabled = 1, 99 | .name = "ANY_ACT_LED", 100 | .gpio_pin = GENERIC_ACT_LED_PIN 101 | }, 102 | }; 103 | 104 | /* led_init sets up gpio pins and starts the background tasks for each LED */ 105 | void led_init(void) { 106 | int num_of_leds = sizeof(led_config) / sizeof(led_config[0]); 107 | 108 | /* LEDs with a pin number of -1 should be disabled. This is a bit 109 | manky but makes the hw.h file considerably more readable. */ 110 | for(int i = 0; i < num_of_leds; i++) { 111 | if (led_config[i].gpio_pin == -1) { 112 | led_config[i].enabled = 0; 113 | } 114 | } 115 | 116 | /* set up GPIO pin, light up each LED */ 117 | 118 | for(int i = 0; i < num_of_leds; i++) { 119 | if (led_config[i].enabled != 1) { 120 | continue; 121 | } 122 | 123 | ESP_LOGI(TAG, "doing %s: %d", led_config[i].name, led_config[i].gpio_pin); 124 | 125 | gpio_reset_pin(led_config[i].gpio_pin); 126 | gpio_set_direction(led_config[i].gpio_pin, GPIO_MODE_OUTPUT); 127 | gpio_set_level(led_config[i].gpio_pin, 1); 128 | 129 | ESP_LOGI(TAG, "done %d", led_config[i].gpio_pin); 130 | } 131 | 132 | vTaskDelay(250 / portTICK_PERIOD_MS); 133 | 134 | /* Now turn off each LED and start the background task for each LED */ 135 | for(int i = 0; i < num_of_leds; i++) { 136 | if (led_config[i].enabled != 1) { 137 | continue; 138 | } 139 | 140 | gpio_set_level(led_config[i].gpio_pin, 0); 141 | xTaskCreate(led_runloop, led_config[i].name, 1024, (void*)i, tskIDLE_PRIORITY, &led_config[i].task); 142 | } 143 | } 144 | 145 | 146 | void led_runloop(void* param) { 147 | LED led = (LED) param; 148 | uint32_t dummy_value = 0; 149 | 150 | /* We just run in a loop, waiting to be notified, flashing our little 151 | light, then going back to sleep again. */ 152 | while(1) { 153 | xTaskNotifyWait(0, 0, &dummy_value, portMAX_DELAY); 154 | gpio_set_level(led_config[led].gpio_pin, (led_config[led].gpio_state + 1) % 2); 155 | vTaskDelay(FLASH_LENGTH / portTICK_PERIOD_MS); 156 | gpio_set_level(led_config[led].gpio_pin, led_config[led].gpio_state); 157 | } 158 | } 159 | 160 | void turn_led_on(LED led) { 161 | led_config[led].gpio_state = 1; 162 | gpio_set_level(led_config[led].gpio_pin, 1); 163 | } 164 | 165 | void turn_led_off(LED led) { 166 | led_config[led].gpio_state = 0; 167 | gpio_set_level(led_config[led].gpio_pin, 0); 168 | } 169 | 170 | 171 | void flash_led_once(LED led) { 172 | int num_of_leds = sizeof(led_config) / sizeof(led_config[0]); 173 | 174 | if (led >= num_of_leds) { 175 | ESP_LOGW(TAG, "attempt to flash illicit LED: %d", led); 176 | } 177 | 178 | if (!led_config[led].enabled) { 179 | return; 180 | } 181 | 182 | xTaskNotify(led_config[led].task, 1, eSetValueWithOverwrite); 183 | 184 | // do we have a second LED we should also flash? 185 | if (led_config[led].also) { 186 | flash_led_once(led_config[led].also); 187 | } 188 | } 189 | 190 | void flash_all_leds_once(void) { 191 | int num_of_leds = sizeof(led_config) / sizeof(led_config[0]); 192 | 193 | for(int i = 0; i < num_of_leds; i++) { 194 | if (led_config[i].enabled != 1) { 195 | continue; 196 | } 197 | 198 | flash_led_once(i); 199 | } 200 | } 201 | 202 | void turn_all_leds_on(void) { 203 | int num_of_leds = sizeof(led_config) / sizeof(led_config[0]); 204 | 205 | for(int i = 0; i < num_of_leds; i++) { 206 | if (led_config[i].enabled != 1) { 207 | continue; 208 | } 209 | 210 | turn_led_on(i); 211 | } 212 | } 213 | 214 | -------------------------------------------------------------------------------- /main/tashtalk.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "freertos/FreeRTOS.h" 6 | #include "freertos/task.h" 7 | #include "esp_log.h" 8 | 9 | #include "tashtalk.h" 10 | #include "packet_types.h" 11 | #include "crc.h" 12 | #include "led.h" 13 | #include "uart.h" 14 | #include "packet_utils.h" 15 | 16 | static const char* TAG = "tashtalk"; 17 | 18 | tashtalk_rx_state_t* new_tashtalk_rx_state(buffer_pool_t* buffer_pool, 19 | QueueHandle_t output_queue) { 20 | 21 | tashtalk_rx_state_t* ns = malloc(sizeof(tashtalk_rx_state_t)); 22 | 23 | if (ns == NULL) { 24 | return NULL; 25 | } 26 | 27 | bzero(ns, sizeof(tashtalk_rx_state_t)); 28 | 29 | ns->buffer_pool = buffer_pool; 30 | ns->output_queue = output_queue; 31 | 32 | return ns; 33 | } 34 | 35 | void append(llap_packet* packet, unsigned char byte) { 36 | if (packet->length < 605) { 37 | packet->packet[packet->length] = byte; 38 | packet->length++; 39 | } else { 40 | ESP_LOGE(TAG, "buffer overflow in packet"); 41 | } 42 | } 43 | 44 | void do_something_sensible_with_packet(tashtalk_rx_state_t* state) { 45 | node_table_t* destinations = get_proxy_nodes(); 46 | 47 | flash_led_once(LT_RX_GREEN); 48 | 49 | // Should we forward this packet? If it's aimed at broadcast or a node 50 | // we've seen on the IP side, yes 51 | uint8_t destination = llap_destination_node(state->packet_in_progress); 52 | if (destination != 255) { 53 | if (!nt_fresh(destinations, destination, 3600)) { 54 | bp_relinquish(state->buffer_pool, (void**)&state->packet_in_progress); 55 | return; 56 | } 57 | } 58 | 59 | uart_check_for_tx_wedge(state->packet_in_progress); 60 | 61 | xQueueSendToBack(state->output_queue, &state->packet_in_progress, portMAX_DELAY); 62 | } 63 | 64 | void feed(tashtalk_rx_state_t* state, unsigned char byte) { 65 | 66 | // do we have a buffer? 67 | if (state->packet_in_progress == NULL) { 68 | state->packet_in_progress = bp_fetch(state->buffer_pool); 69 | crc_state_init(&state->crc); 70 | } 71 | 72 | // buggered if I know what else to do here other than go in circles 73 | while (state->packet_in_progress == NULL) { 74 | vTaskDelay(10 / portTICK_PERIOD_MS); 75 | state->packet_in_progress = bp_fetch(state->buffer_pool); 76 | crc_state_init(&state->crc); 77 | ESP_LOGE(TAG, "around and around and around we go"); 78 | } 79 | 80 | // Are we in an escape state? 81 | if (state->in_escape) { 82 | switch(byte) { 83 | case 0xFF: 84 | // 0x00 0xFF is a literal 0x00 byte 85 | append(state->packet_in_progress, 0x00); 86 | crc_state_append(&state->crc, 0x00); 87 | break; 88 | 89 | case 0xFD: 90 | // 0x00 0xFD is a complete frame 91 | if (!crc_state_ok(&state->crc)) { 92 | ESP_LOGE(TAG, "/!\\ CRC fail: %d", state->crc); 93 | flash_led_once(LT_RED_LED); 94 | } 95 | 96 | do_something_sensible_with_packet(state); 97 | state->packet_in_progress = NULL; 98 | break; 99 | 100 | case 0xFE: 101 | // 0x00 0xFD is a complete frame 102 | ESP_LOGI(TAG, "framing error of %d bytes", 103 | state->packet_in_progress->length); 104 | flash_led_once(LT_RED_LED); 105 | 106 | bp_relinquish(state->buffer_pool, (void**) &state->packet_in_progress); 107 | state->packet_in_progress = NULL; 108 | break; 109 | 110 | case 0xFA: 111 | // 0x00 0xFD is a complete frame 112 | ESP_LOGI(TAG, "frame abort of %d bytes", 113 | state->packet_in_progress->length); 114 | flash_led_once(LT_RED_LED); 115 | 116 | bp_relinquish(state->buffer_pool, (void**) &state->packet_in_progress); 117 | state->packet_in_progress = NULL; 118 | break; 119 | } 120 | 121 | state->in_escape = false; 122 | } else if (byte == 0x00) { 123 | state->in_escape = true; 124 | } else { 125 | append(state->packet_in_progress, byte); 126 | crc_state_append(&state->crc, byte); 127 | } 128 | } 129 | 130 | void feed_all(tashtalk_rx_state_t* state, unsigned char* buf, int count) { 131 | for (int i = 0; i < count; i++) { 132 | feed(state, buf[i]); 133 | } 134 | } 135 | 136 | bool tashtalk_tx_validate(llap_packet* packet) { 137 | if (packet->length < 5) { 138 | // Too short 139 | ESP_LOGE(TAG, "tx packet too short"); 140 | return false; 141 | } 142 | 143 | if (packet->length == 5 && !(packet->packet[2] & 0x80)) { 144 | // 3 byte packet is not a control packet 145 | ESP_LOGE(TAG, "tx 3-byte non-control packet, wut?"); 146 | flash_led_once(LT_RED_LED); 147 | return false; 148 | } 149 | 150 | if ((packet->packet[2] & 0x80) && packet->length != 5) { 151 | // too long control frame 152 | ESP_LOGE(TAG, "tx too-long control packet, wut?"); 153 | flash_led_once(LT_RED_LED); 154 | return false; 155 | } 156 | 157 | if (packet->length == 6) { 158 | // impossible packet length 159 | ESP_LOGE(TAG, "tx impossible packet length, wut?"); 160 | flash_led_once(LT_RED_LED); 161 | return false; 162 | } 163 | 164 | if (packet->length >= 7 && (((packet->packet[3] & 0x3) << 8) | packet->packet[4]) != packet->length - 5) { 165 | // packet length does not match claimed length 166 | ESP_LOGE(TAG, "tx length field (%d) does not match actual packet length (%d)", (((packet->packet[3] & 0x3) << 8) | packet->packet[4]), packet->length - 5); 167 | flash_led_once(LT_RED_LED); 168 | return false; 169 | } 170 | 171 | // check the CRC 172 | crc_state_t crc; 173 | crc_state_init(&crc); 174 | crc_state_append_all(&crc, packet->packet, packet->length); 175 | if (!crc_state_ok(&crc)) { 176 | ESP_LOGE(TAG, "bad CRC on tx: IP bug?"); 177 | flash_led_once(LT_RED_LED); 178 | return false; 179 | } 180 | 181 | return true; 182 | } 183 | -------------------------------------------------------------------------------- /main/packet_utils.c: -------------------------------------------------------------------------------- 1 | #include "packet_utils.h" 2 | 3 | #define NUM_LEN_GUARD(x) if (packet->length <= (x)) { return 0; } 4 | #define BOOL_LEN_GUARD(x) if (packet->length <= (x)) { return false; } 5 | 6 | bool skip_pstring(llap_packet* packet, int* cursor) { 7 | BOOL_LEN_GUARD(*cursor); 8 | *cursor += packet->packet[*cursor] + 1; 9 | return true; 10 | } 11 | 12 | bool read_pstring(llap_packet* packet, int* cursor, unsigned char** str) { 13 | BOOL_LEN_GUARD(*cursor); 14 | BOOL_LEN_GUARD(*cursor + packet->packet[*cursor]); 15 | *str = &packet->packet[*cursor]; 16 | *cursor += packet->packet[*cursor] + 1; 17 | return true; 18 | } 19 | 20 | 21 | bool read_uint16(llap_packet* packet, int* cursor, uint16_t* result) { 22 | BOOL_LEN_GUARD(*cursor + 1); 23 | *result = ((uint16_t)packet->packet[*cursor] << 8) + ((uint16_t)packet->packet[*cursor+1]); 24 | *cursor += 2; 25 | return true; 26 | } 27 | 28 | bool read_uint8(llap_packet* packet, int* cursor, uint8_t* result) { 29 | BOOL_LEN_GUARD(*cursor); 30 | *result = packet->packet[*cursor]; 31 | *cursor += 1; 32 | return true; 33 | } 34 | 35 | 36 | /* LLAP */ 37 | 38 | int llap_type(llap_packet* packet) { 39 | NUM_LEN_GUARD(2); 40 | return packet->packet[2]; 41 | } 42 | 43 | int llap_destination_node(llap_packet* packet) { 44 | NUM_LEN_GUARD(0); 45 | return packet->packet[0]; 46 | } 47 | 48 | 49 | /* DDP */ 50 | 51 | #define LLAP_TYPE_OFFSET 2 52 | #define LLAP_PAYLOAD_OFFSET 3 53 | 54 | bool is_ddp_packet(llap_packet* packet) { 55 | // packets of length 3 or less can't be DDP packets 56 | if (packet->length <= 3) { 57 | return false; 58 | } 59 | 60 | 61 | if (packet->packet[LLAP_TYPE_OFFSET] == 1 || 62 | packet->packet[LLAP_TYPE_OFFSET] == 2) { 63 | 64 | return true; 65 | } 66 | 67 | return false; 68 | } 69 | 70 | bool ddp_has_long_header(llap_packet* packet) { 71 | return packet->packet[LLAP_TYPE_OFFSET] == 2; 72 | } 73 | 74 | uint8_t ddp_type(llap_packet* packet) { 75 | if (ddp_has_long_header(packet)) { 76 | NUM_LEN_GUARD(LLAP_PAYLOAD_OFFSET + 12); 77 | return packet->packet[LLAP_PAYLOAD_OFFSET + 12]; 78 | } else { 79 | NUM_LEN_GUARD(LLAP_PAYLOAD_OFFSET + 4); 80 | return packet->packet[LLAP_PAYLOAD_OFFSET + 4]; 81 | } 82 | } 83 | 84 | uint8_t ddp_destination_node(llap_packet* packet) { 85 | if (ddp_has_long_header(packet)) { 86 | NUM_LEN_GUARD(LLAP_PAYLOAD_OFFSET + 8); 87 | return packet->packet[LLAP_PAYLOAD_OFFSET + 8]; 88 | } else { 89 | return packet->packet[0]; 90 | } 91 | } 92 | 93 | uint8_t ddp_destination_socket(llap_packet* packet) { 94 | if (ddp_has_long_header(packet)) { 95 | NUM_LEN_GUARD(LLAP_PAYLOAD_OFFSET + 10); 96 | return packet->packet[LLAP_PAYLOAD_OFFSET + 10]; 97 | } else { 98 | NUM_LEN_GUARD(LLAP_PAYLOAD_OFFSET + 4); 99 | return packet->packet[LLAP_PAYLOAD_OFFSET + 2]; 100 | } 101 | } 102 | 103 | 104 | int ddp_payload_offset(llap_packet* packet) { 105 | if (ddp_has_long_header(packet)) { 106 | return LLAP_PAYLOAD_OFFSET + 13; 107 | } else { 108 | return LLAP_PAYLOAD_OFFSET + 5; 109 | } 110 | } 111 | 112 | 113 | /* NBP */ 114 | 115 | bool is_nbp_packet(llap_packet* packet) { 116 | return is_ddp_packet(packet) && (ddp_type(packet) == 2); 117 | } 118 | 119 | int nbp_function_code(llap_packet* packet) { 120 | int p = ddp_payload_offset(packet); 121 | 122 | NUM_LEN_GUARD(p); 123 | 124 | // Top nybble of the first byte of the packet 125 | return packet->packet[p] >> 4; 126 | } 127 | 128 | int nbp_tuple_count(llap_packet* packet) { 129 | int p = ddp_payload_offset(packet); 130 | 131 | NUM_LEN_GUARD(p); 132 | 133 | // Top nybble of the first byte of the packet 134 | return packet->packet[p] & 0x0F; 135 | } 136 | 137 | int nbp_id(llap_packet* packet) { 138 | int p = ddp_payload_offset(packet); 139 | NUM_LEN_GUARD(p+1); 140 | return packet->packet[p+1]; 141 | } 142 | 143 | nbp_tuple_t nbp_tuple(llap_packet* packet, int tuple) { 144 | nbp_tuple_t ret = {0}; 145 | bool ran_out_of_packet = false; 146 | int cursor = ddp_payload_offset(packet) + 2; 147 | 148 | // skip tuples until we get to the one we want 149 | for (int i = 0; i < tuple; i++) { 150 | // skip network metadata and enumerator 151 | cursor += 5; 152 | 153 | // and skip three pascal strings 154 | for (int j = 0; j < 3; j++) { 155 | if (!skip_pstring(packet, &cursor)) { 156 | ran_out_of_packet = true; 157 | break; 158 | } 159 | } 160 | 161 | if (ran_out_of_packet) { 162 | break; 163 | } 164 | } 165 | 166 | // At this point either our cursor is pointing at the beginning of the 167 | // tuple we want, or we ran out of packet. If the latter, return an 168 | // nbp_tuple_t with no ok set. 169 | if (ran_out_of_packet) { 170 | return ret; 171 | } 172 | 173 | if (!read_uint16(packet, &cursor, &ret.network)) { 174 | return ret; 175 | } 176 | 177 | if (!read_uint8(packet, &cursor, &ret.node)) { 178 | return ret; 179 | } 180 | 181 | if (!read_uint8(packet, &cursor, &ret.socket)) { 182 | return ret; 183 | } 184 | 185 | if (!read_uint8(packet, &cursor, &ret.enumerator)) { 186 | return ret; 187 | } 188 | 189 | if (!read_pstring(packet, &cursor, &ret.object)) { 190 | return ret; 191 | } 192 | 193 | if (!read_pstring(packet, &cursor, &ret.type)) { 194 | return ret; 195 | } 196 | 197 | 198 | if (!read_pstring(packet, &cursor, &ret.zone)) { 199 | return ret; 200 | } 201 | 202 | ret.ok = true; 203 | return ret; 204 | } 205 | 206 | 207 | /* ATP */ 208 | 209 | bool is_atp_packet(llap_packet* packet) { 210 | return is_ddp_packet(packet) && (ddp_type(packet) == 3); 211 | } 212 | 213 | int atp_function_code(llap_packet* packet) { 214 | int p = ddp_payload_offset(packet); 215 | 216 | NUM_LEN_GUARD(p); 217 | 218 | // Top two bits of the first byte of the packet 219 | return (packet->packet[p] & 0xC0) >> 6; 220 | } 221 | 222 | int atp_payload_offset(llap_packet* packet) { 223 | return ddp_payload_offset(packet) + 8; 224 | } 225 | -------------------------------------------------------------------------------- /main/apps.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "freertos/FreeRTOS.h" 5 | #include "freertos/task.h" 6 | #include "esp_system.h" 7 | #include "esp_log.h" 8 | 9 | #include "apps.h" 10 | #include "scan_manager.h" 11 | #include "buffer_pool.h" 12 | #include "packet_types.h" 13 | #include "packet_utils.h" 14 | #include "crc.h" 15 | #include "crc32.h" 16 | #include "pstrings.h" 17 | #include "storage.h" 18 | 19 | #define REQUIRE(x) if(!(x)) { return false; } 20 | 21 | const char* TAG = "APPS"; 22 | 23 | QueueHandle_t qFromUART, qToUART, qToUDP; 24 | buffer_pool_t* buffer_pool; 25 | 26 | void send_fake_nbp_LkUpReply(uint8_t node, uint8_t socket, uint8_t enumerator, 27 | uint8_t nbp_id, char* object, char* type, char* zone) { 28 | 29 | llap_packet* packet = bp_fetch(buffer_pool); 30 | if (packet == NULL) { 31 | // um I don't know what to do, let's bail out and hope it goes away 32 | return; 33 | } 34 | 35 | // first of all, let's work out how long this packet is going to be 36 | uint16_t object_len = strlen(object); 37 | uint16_t type_len = strlen(type); 38 | uint16_t zone_len = strlen(zone); 39 | 40 | uint16_t tuple_len = 5 + // network information 41 | 3 + // string length bytes 42 | object_len + type_len + zone_len; 43 | 44 | uint16_t nbp_packet_len = tuple_len + 2; 45 | uint16_t ddp_packet_len = nbp_packet_len + 5; 46 | uint16_t llap_packet_len = ddp_packet_len + 3; 47 | 48 | // now start populating the packet. LLAP headers first: 49 | packet->length = llap_packet_len; 50 | packet->packet[0] = node; 51 | packet->packet[1] = 129; // see if we can get away with this 52 | packet->packet[2] = 1; // 1 -> DDP, short header. 53 | 54 | // DDP headers next: 55 | packet->packet[3] = 0; 56 | packet->packet[4] = ddp_packet_len; 57 | 58 | packet->packet[5] = socket; 59 | packet->packet[6] = 2; // source socket number 60 | packet->packet[7] = 2; // DDP type for NBP 61 | 62 | // NBP header: 63 | packet->packet[8] = 0x31; // lkup-reply, 1 tuple 64 | packet->packet[9] = nbp_id; 65 | 66 | // NBP tuple. We fake this based on the crc32 of the object name 67 | uint32_t my_crc32 = crc32buf(object, object_len); 68 | 69 | packet->packet[10] = my_crc32 & 0xFF000000 >> 24; // network, msb 70 | packet->packet[11] = my_crc32 & 0x00FF0000 >> 16; 71 | packet->packet[12] = my_crc32 & 0x0000FF00 >> 8; // node ID 72 | packet->packet[13] = my_crc32 & 0x000000FF; // socket number 73 | packet->packet[14] = enumerator; 74 | 75 | // add names into tuple 76 | int cursor = 15; 77 | 78 | // object 79 | packet->packet[cursor++] = object_len; 80 | for (int i = 0; i < object_len; i++) { 81 | packet->packet[cursor++] = object[i]; 82 | } 83 | 84 | // type 85 | packet->packet[cursor++] = type_len; 86 | for (int i = 0; i < type_len; i++) { 87 | packet->packet[cursor++] = type[i]; 88 | } 89 | 90 | // zone 91 | packet->packet[cursor++] = zone_len; 92 | for (int i = 0; i < zone_len; i++) { 93 | packet->packet[cursor++] = zone[i]; 94 | } 95 | 96 | // finally, add the crc 97 | crc_state_t crc; 98 | crc_state_init(&crc); 99 | crc_state_append_all(&crc, packet->packet, packet->length); 100 | 101 | packet->length += 2; 102 | packet->packet[cursor++] = crc_state_byte_1(&crc); 103 | packet->packet[cursor++] = crc_state_byte_2(&crc); 104 | 105 | xQueueSendToBack(qToUART, &packet, portMAX_DELAY); 106 | vTaskDelay(1 / portTICK_PERIOD_MS); 107 | 108 | //bp_relinquish(buffer_pool, &packet); 109 | } 110 | 111 | bool is_our_nbp_lkup(llap_packet* packet, char* nbptype, nbp_return_t* addr) { 112 | // First, we check whether this is a broadcast NBP lookup with at least one 113 | // tuple. 114 | REQUIRE(is_nbp_packet(packet)); 115 | REQUIRE(ddp_destination_node(packet) == 255); 116 | REQUIRE(nbp_function_code(packet) == NBP_LKUP); 117 | REQUIRE(nbp_tuple_count(packet) > 0); 118 | 119 | // Now we extract the first tuple and check whether the type is the one 120 | // our caller wants. 121 | nbp_tuple_t tuple = nbp_tuple(packet, 0); 122 | if (!tuple.ok) { 123 | return false; 124 | } 125 | 126 | // let's check our type string is actually a protocol-compliant length. 127 | // just in case. 128 | if (pstrlen(tuple.type) > 32) { 129 | return false; 130 | } 131 | 132 | // Now compare. Note that the tuple contains pascal strings, so we have 133 | // to convert it. 134 | char typename[33]; 135 | pstrncpy(typename, tuple.type, 32); 136 | if (strncmp(typename, nbptype, 33) != 0) { 137 | return false; 138 | } 139 | 140 | // If we get to this point, we care about this update, so fill in the 141 | // return address for the caller. 142 | addr->node = tuple.node; 143 | addr->socket = tuple.socket; 144 | addr->nbp_id = nbp_id(packet); 145 | 146 | return true; 147 | } 148 | 149 | bool handle_configuration_packet(llap_packet* packet) { 150 | // We're looking for an ATP TREQ packet... 151 | REQUIRE(is_atp_packet(packet)); 152 | REQUIRE(atp_function_code(packet) == ATP_TREQ); 153 | 154 | // ... aimed at a broadcast address ... 155 | REQUIRE(ddp_destination_node(packet) == 255); 156 | 157 | // ... with a destination socket of 4 ... 158 | REQUIRE(ddp_destination_socket(packet) == 4); 159 | 160 | unsigned char* payload = &packet->packet[atp_payload_offset(packet)]; 161 | 162 | // ... with a payload that begins with "airtalk setap" as a pascal string 163 | if (memcmp(payload + 1, "airtalk setap", 13) != 0) { 164 | return false; 165 | } 166 | 167 | // copy stuff out of the payload 168 | char ssid[64] = {0}; 169 | char passwd[64] = {0}; 170 | pstrncpy(ssid, payload+14, 63); 171 | pstrncpy(passwd, payload + 14 + pstrlen(payload+14)+1, 63); 172 | 173 | ESP_LOGI(TAG, "wifi config request r'd, ssid: %s, pass: %s", ssid, passwd); 174 | store_wifi_details(ssid, passwd); 175 | esp_restart(); 176 | 177 | // we never actually return but C wants us to pretend 178 | return false; 179 | } 180 | 181 | void apps_runloop(void* unused) { 182 | llap_packet* packet = NULL; 183 | nbp_return_t nbp_addr; 184 | 185 | while(1) { 186 | if (xQueueReceive(qFromUART, &packet, portMAX_DELAY)) { 187 | if (is_our_nbp_lkup(packet, "AirTalkAP", &nbp_addr)) { 188 | ESP_LOGI(TAG, "nbp lkup"); 189 | scan_and_send_results(nbp_addr.node, nbp_addr.socket, nbp_addr.nbp_id); 190 | } 191 | 192 | if (handle_configuration_packet(packet)) { 193 | bp_relinquish(buffer_pool, (void**)&packet); 194 | continue; 195 | } 196 | 197 | xQueueSendToBack(qToUDP, &packet, portMAX_DELAY); 198 | } 199 | } 200 | } 201 | 202 | void start_apps(buffer_pool_t* packet_pool, QueueHandle_t fromUART, 203 | QueueHandle_t toUART, QueueHandle_t toUDP) { 204 | 205 | qFromUART = fromUART; 206 | qToUART = toUART; 207 | qToUDP = toUDP; 208 | buffer_pool = packet_pool; 209 | 210 | xTaskCreate(&apps_runloop, "APPS", 4096, (void*)packet_pool, 5, NULL); 211 | } 212 | -------------------------------------------------------------------------------- /main/ip.c: -------------------------------------------------------------------------------- 1 | #include "esp_log.h" 2 | 3 | #include "lwip/err.h" 4 | #include "lwip/sockets.h" 5 | #include "lwip/sys.h" 6 | 7 | #include "packet_types.h" 8 | #include "buffer_pool.h" 9 | #include "wifi.h" 10 | #include "led.h" 11 | #include "crc.h" 12 | #include "uart.h" 13 | #include "node_table.h" 14 | 15 | static const char* TAG = "ip"; 16 | 17 | int udp_sock = -1; 18 | buffer_pool_t* packet_pool; 19 | TaskHandle_t udp_rx_task = NULL; 20 | TaskHandle_t udp_tx_task = NULL; 21 | 22 | QueueHandle_t udp_tx_queue = NULL; 23 | QueueHandle_t udp_rx_queue = NULL; 24 | 25 | node_table_t forward_for_nodes = { 0 }; 26 | 27 | void udp_error() { 28 | flash_led_once(UDP_RED_LED); 29 | } 30 | 31 | void init_udp(void) { 32 | struct sockaddr_in saddr = { 0 }; 33 | int sock = -1; 34 | int err = 0; 35 | struct in_addr outgoing_addr = { 0 }; 36 | esp_netif_ip_info_t ip_info = { 0 }; 37 | 38 | /* bail out early if we don't have a wifi network interface yet */ 39 | 40 | if (get_active_net_intf() == NULL) { 41 | ESP_LOGE(TAG, "wireless network interface does not exist."); 42 | udp_error(); 43 | return; 44 | } 45 | 46 | /* or if doesn't think it's ready */ 47 | if (!is_net_if_ready()) { 48 | ESP_LOGE(TAG, "wireless network interface is not ready."); 49 | udp_error(); 50 | return; 51 | }; 52 | 53 | /* or if we can't get its IP */ 54 | err = esp_netif_get_ip_info(get_active_net_intf(), &ip_info); 55 | if (err != ESP_OK) { 56 | ESP_LOGE(TAG, "wireless network interface wouldn't tell us its IP."); 57 | udp_error(); 58 | return; 59 | } 60 | 61 | inet_addr_from_ip4addr(&outgoing_addr, &ip_info.ip); 62 | 63 | /* create the multicast socket and twiddle its socket options */ 64 | 65 | sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); 66 | if (sock < 0) { 67 | ESP_LOGE(TAG, "socket() failed. error: %d", errno); 68 | udp_error(); 69 | return; 70 | } 71 | 72 | saddr.sin_family = PF_INET; 73 | saddr.sin_port = htons(1954); 74 | saddr.sin_addr.s_addr = htonl(INADDR_ANY); 75 | err = bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in)); 76 | if (err < 0) { 77 | ESP_LOGE(TAG, "bind() failed, error: %d", errno); 78 | udp_error(); 79 | goto err_cleanup; 80 | } 81 | 82 | uint8_t ttl = 1; 83 | setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(uint8_t)); 84 | if (err < 0) { 85 | ESP_LOGE(TAG, "setsockopt(...IP_MULTICAST_TTL...) failed, error: %d", errno); 86 | udp_error(); 87 | goto err_cleanup; 88 | } 89 | 90 | setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &outgoing_addr, sizeof(struct in_addr)); 91 | if (err < 0) { 92 | ESP_LOGE(TAG, "setsockopt(...IP_MULTICAST_IF...) failed, error: %d", errno); 93 | udp_error(); 94 | goto err_cleanup; 95 | } 96 | 97 | 98 | struct ip_mreq imreq = { 0 }; 99 | imreq.imr_interface.s_addr = IPADDR_ANY; 100 | err = inet_aton("239.192.76.84", &imreq.imr_multiaddr.s_addr); 101 | if (err != 1) { 102 | ESP_LOGE(TAG, "inet_aton failed, error: %d", errno); 103 | udp_error(); 104 | goto err_cleanup; 105 | } 106 | 107 | err = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, 108 | &imreq, sizeof(struct ip_mreq)); 109 | if (err < 0) { 110 | ESP_LOGE(TAG, "setsockopt(...IP_ADD_MEMBERSHIP...) failed, error: %d", errno); 111 | udp_error(); 112 | goto err_cleanup; 113 | } 114 | 115 | udp_sock = sock; 116 | 117 | ESP_LOGI(TAG, "multicast socket now ready."); 118 | 119 | return; 120 | 121 | 122 | err_cleanup: 123 | close(sock); 124 | udp_error(); 125 | return; 126 | 127 | } 128 | 129 | void udp_rx_runloop(void *pvParameters) { 130 | ESP_LOGI(TAG, "starting LToUDP listener"); 131 | init_udp(); 132 | 133 | while(1) { 134 | /* retry if we need to */ 135 | while(udp_sock == -1) { 136 | ESP_LOGE(TAG, "setting up LToUDP listener failed. Retrying..."); 137 | vTaskDelay(1000 / portTICK_PERIOD_MS); 138 | init_udp(); 139 | } 140 | 141 | while(1) { 142 | unsigned char buffer[603+4]; // 603 for LLAP (per Inside Appletalk) + 143 | // 4 bytes LToUDP header 144 | int len = recv(udp_sock, buffer, sizeof(buffer), 0); 145 | if (len < 0) { 146 | flash_led_once(UDP_RED_LED); 147 | break; 148 | } 149 | if (len > 609) { 150 | flash_led_once(UDP_RED_LED); 151 | ESP_LOGE(TAG, "packet too long: %d", len); 152 | continue; 153 | } 154 | if (len > 7) { 155 | flash_led_once(UDP_RX_GREEN); 156 | 157 | // Have we seen any traffic from this node? Is it one 158 | // we actually want to send traffic to? If not, go around 159 | // again. 160 | if (!nt_fresh(&forward_for_nodes, buffer[4], 3600) && 161 | (buffer[4] != 255)) { 162 | continue; 163 | } 164 | 165 | // fetch an empty buffer from the pool and fill it with 166 | // packet info 167 | llap_packet* lp = bp_fetch(packet_pool); 168 | if (lp != NULL) { 169 | crc_state_t crc; 170 | 171 | // copy the LLAP packet into the packet buffer 172 | lp->length = len-2; // -4 for LToUDP tag, +2 for the FCS 173 | memcpy(lp->packet, buffer+4, len-4); 174 | 175 | // now calculate the CRC 176 | crc_state_init(&crc); 177 | crc_state_append_all(&crc, buffer+4, len-4); 178 | lp->packet[lp->length - 2] = crc_state_byte_1(&crc); 179 | lp->packet[lp->length - 1] = crc_state_byte_2(&crc); 180 | 181 | xQueueSendToBack(udp_rx_queue, &lp, portMAX_DELAY); 182 | } 183 | } 184 | } 185 | 186 | close(udp_sock); 187 | udp_sock = -1; 188 | } 189 | } 190 | 191 | void udp_tx_runloop(void *pvParameters) { 192 | llap_packet* packet = NULL; 193 | unsigned char outgoing_buffer[605 + 4] = { 0 }; // LToUDP has 4 byte header 194 | struct sockaddr_in dest_addr = {0}; 195 | int err = 0; 196 | 197 | dest_addr.sin_addr.s_addr = inet_addr("239.192.76.84"); 198 | dest_addr.sin_family = AF_INET; 199 | dest_addr.sin_port = htons(1954); 200 | 201 | ESP_LOGI(TAG, "starting LToUDP sender"); 202 | 203 | while(1) { 204 | xQueueReceive(udp_tx_queue, &packet, portMAX_DELAY); 205 | 206 | if (packet == NULL) { 207 | continue; 208 | } 209 | 210 | // Add this to the "known senders" list we're forwarding traffic for 211 | if (packet->length > 3) { 212 | nt_touch(&forward_for_nodes, packet->packet[1]); 213 | } 214 | 215 | // Should we pass this on? 216 | // If it's an RTS or a CTS packet, then no 217 | if (packet->length == 5) { 218 | if (packet->packet[2] == 0x84 || packet->packet[2] == 0x85) { 219 | goto skip_processing; 220 | } 221 | } 222 | 223 | /* TODO: if this is an ENQ for a node ID that is not in our recently 224 | seen table, pass it on, otherwise block all ENQs */ 225 | 226 | if (udp_sock != -1) { 227 | memcpy(outgoing_buffer+4, packet->packet, packet->length); 228 | 229 | err = sendto(udp_sock, outgoing_buffer, packet->length+2, 0, 230 | (struct sockaddr *)&dest_addr, sizeof(dest_addr)); 231 | if (err < 0) { 232 | ESP_LOGE(TAG, "error: sendto: errno %d", errno); 233 | flash_led_once(UDP_RED_LED); 234 | } else { 235 | flash_led_once(UDP_TX_GREEN); 236 | } 237 | err = 0; 238 | } 239 | 240 | skip_processing: 241 | bp_relinquish(packet_pool, (void**)&packet); 242 | } 243 | } 244 | 245 | 246 | void start_udp(buffer_pool_t* pool, QueueHandle_t txQueue, QueueHandle_t rxQueue) { 247 | packet_pool = pool; 248 | udp_tx_queue = txQueue; 249 | udp_rx_queue = rxQueue; 250 | xTaskCreate(&udp_rx_runloop, "LToUDP-rx", 4096, NULL, 5, &udp_rx_task); 251 | xTaskCreate(&udp_tx_runloop, "LToUDP-tx", 4096, NULL, 5, &udp_tx_task); 252 | 253 | } 254 | -------------------------------------------------------------------------------- /hardware/LICENSE: -------------------------------------------------------------------------------- 1 | CERN Open Hardware Licence Version 2 - Strongly Reciprocal 2 | 3 | 4 | Preamble 5 | 6 | CERN has developed this licence to promote collaboration among 7 | hardware designers and to provide a legal tool which supports the 8 | freedom to use, study, modify, share and distribute hardware designs 9 | and products based on those designs. Version 2 of the CERN Open 10 | Hardware Licence comes in three variants: CERN-OHL-P (permissive); and 11 | two reciprocal licences: CERN-OHL-W (weakly reciprocal) and this 12 | licence, CERN-OHL-S (strongly reciprocal). 13 | 14 | The CERN-OHL-S is copyright CERN 2020. Anyone is welcome to use it, in 15 | unmodified form only. 16 | 17 | Use of this Licence does not imply any endorsement by CERN of any 18 | Licensor or their designs nor does it imply any involvement by CERN in 19 | their development. 20 | 21 | 22 | 1 Definitions 23 | 24 | 1.1 'Licence' means this CERN-OHL-S. 25 | 26 | 1.2 'Compatible Licence' means 27 | 28 | a) any earlier version of the CERN Open Hardware licence, or 29 | 30 | b) any version of the CERN-OHL-S, or 31 | 32 | c) any licence which permits You to treat the Source to which 33 | it applies as licensed under CERN-OHL-S provided that on 34 | Conveyance of any such Source, or any associated Product You 35 | treat the Source in question as being licensed under 36 | CERN-OHL-S. 37 | 38 | 1.3 'Source' means information such as design materials or digital 39 | code which can be applied to Make or test a Product or to 40 | prepare a Product for use, Conveyance or sale, regardless of its 41 | medium or how it is expressed. It may include Notices. 42 | 43 | 1.4 'Covered Source' means Source that is explicitly made available 44 | under this Licence. 45 | 46 | 1.5 'Product' means any device, component, work or physical object, 47 | whether in finished or intermediate form, arising from the use, 48 | application or processing of Covered Source. 49 | 50 | 1.6 'Make' means to create or configure something, whether by 51 | manufacture, assembly, compiling, loading or applying Covered 52 | Source or another Product or otherwise. 53 | 54 | 1.7 'Available Component' means any part, sub-assembly, library or 55 | code which: 56 | 57 | a) is licensed to You as Complete Source under a Compatible 58 | Licence; or 59 | 60 | b) is available, at the time a Product or the Source containing 61 | it is first Conveyed, to You and any other prospective 62 | licensees 63 | 64 | i) as a physical part with sufficient rights and 65 | information (including any configuration and 66 | programming files and information about its 67 | characteristics and interfaces) to enable it either to 68 | be Made itself, or to be sourced and used to Make the 69 | Product; or 70 | ii) as part of the normal distribution of a tool used to 71 | design or Make the Product. 72 | 73 | 1.8 'Complete Source' means the set of all Source necessary to Make 74 | a Product, in the preferred form for making modifications, 75 | including necessary installation and interfacing information 76 | both for the Product, and for any included Available Components. 77 | If the format is proprietary, it must also be made available in 78 | a format (if the proprietary tool can create it) which is 79 | viewable with a tool available to potential licensees and 80 | licensed under a licence approved by the Free Software 81 | Foundation or the Open Source Initiative. Complete Source need 82 | not include the Source of any Available Component, provided that 83 | You include in the Complete Source sufficient information to 84 | enable a recipient to Make or source and use the Available 85 | Component to Make the Product. 86 | 87 | 1.9 'Source Location' means a location where a Licensor has placed 88 | Covered Source, and which that Licensor reasonably believes will 89 | remain easily accessible for at least three years for anyone to 90 | obtain a digital copy. 91 | 92 | 1.10 'Notice' means copyright, acknowledgement and trademark notices, 93 | Source Location references, modification notices (subsection 94 | 3.3(b)) and all notices that refer to this Licence and to the 95 | disclaimer of warranties that are included in the Covered 96 | Source. 97 | 98 | 1.11 'Licensee' or 'You' means any person exercising rights under 99 | this Licence. 100 | 101 | 1.12 'Licensor' means a natural or legal person who creates or 102 | modifies Covered Source. A person may be a Licensee and a 103 | Licensor at the same time. 104 | 105 | 1.13 'Convey' means to communicate to the public or distribute. 106 | 107 | 108 | 2 Applicability 109 | 110 | 2.1 This Licence governs the use, copying, modification, Conveying 111 | of Covered Source and Products, and the Making of Products. By 112 | exercising any right granted under this Licence, You irrevocably 113 | accept these terms and conditions. 114 | 115 | 2.2 This Licence is granted by the Licensor directly to You, and 116 | shall apply worldwide and without limitation in time. 117 | 118 | 2.3 You shall not attempt to restrict by contract or otherwise the 119 | rights granted under this Licence to other Licensees. 120 | 121 | 2.4 This Licence is not intended to restrict fair use, fair dealing, 122 | or any other similar right. 123 | 124 | 125 | 3 Copying, Modifying and Conveying Covered Source 126 | 127 | 3.1 You may copy and Convey verbatim copies of Covered Source, in 128 | any medium, provided You retain all Notices. 129 | 130 | 3.2 You may modify Covered Source, other than Notices, provided that 131 | You irrevocably undertake to make that modified Covered Source 132 | available from a Source Location should You Convey a Product in 133 | circumstances where the recipient does not otherwise receive a 134 | copy of the modified Covered Source. In each case subsection 3.3 135 | shall apply. 136 | 137 | You may only delete Notices if they are no longer applicable to 138 | the corresponding Covered Source as modified by You and You may 139 | add additional Notices applicable to Your modifications. 140 | Including Covered Source in a larger work is modifying the 141 | Covered Source, and the larger work becomes modified Covered 142 | Source. 143 | 144 | 3.3 You may Convey modified Covered Source (with the effect that You 145 | shall also become a Licensor) provided that You: 146 | 147 | a) retain Notices as required in subsection 3.2; 148 | 149 | b) add a Notice to the modified Covered Source stating that You 150 | have modified it, with the date and brief description of how 151 | You have modified it; 152 | 153 | c) add a Source Location Notice for the modified Covered Source 154 | if You Convey in circumstances where the recipient does not 155 | otherwise receive a copy of the modified Covered Source; and 156 | 157 | d) license the modified Covered Source under the terms and 158 | conditions of this Licence (or, as set out in subsection 159 | 8.3, a later version, if permitted by the licence of the 160 | original Covered Source). Such modified Covered Source must 161 | be licensed as a whole, but excluding Available Components 162 | contained in it, which remain licensed under their own 163 | applicable licences. 164 | 165 | 166 | 4 Making and Conveying Products 167 | 168 | You may Make Products, and/or Convey them, provided that You either 169 | provide each recipient with a copy of the Complete Source or ensure 170 | that each recipient is notified of the Source Location of the Complete 171 | Source. That Complete Source is Covered Source, and You must 172 | accordingly satisfy Your obligations set out in subsection 3.3. If 173 | specified in a Notice, the Product must visibly and securely display 174 | the Source Location on it or its packaging or documentation in the 175 | manner specified in that Notice. 176 | 177 | 178 | 5 Research and Development 179 | 180 | You may Convey Covered Source, modified Covered Source or Products to 181 | a legal entity carrying out development, testing or quality assurance 182 | work on Your behalf provided that the work is performed on terms which 183 | prevent the entity from both using the Source or Products for its own 184 | internal purposes and Conveying the Source or Products or any 185 | modifications to them to any person other than You. Any modifications 186 | made by the entity shall be deemed to be made by You pursuant to 187 | subsection 3.2. 188 | 189 | 190 | 6 DISCLAIMER AND LIABILITY 191 | 192 | 6.1 DISCLAIMER OF WARRANTY -- The Covered Source and any Products 193 | are provided 'as is' and any express or implied warranties, 194 | including, but not limited to, implied warranties of 195 | merchantability, of satisfactory quality, non-infringement of 196 | third party rights, and fitness for a particular purpose or use 197 | are disclaimed in respect of any Source or Product to the 198 | maximum extent permitted by law. The Licensor makes no 199 | representation that any Source or Product does not or will not 200 | infringe any patent, copyright, trade secret or other 201 | proprietary right. The entire risk as to the use, quality, and 202 | performance of any Source or Product shall be with You and not 203 | the Licensor. This disclaimer of warranty is an essential part 204 | of this Licence and a condition for the grant of any rights 205 | granted under this Licence. 206 | 207 | 6.2 EXCLUSION AND LIMITATION OF LIABILITY -- The Licensor shall, to 208 | the maximum extent permitted by law, have no liability for 209 | direct, indirect, special, incidental, consequential, exemplary, 210 | punitive or other damages of any character including, without 211 | limitation, procurement of substitute goods or services, loss of 212 | use, data or profits, or business interruption, however caused 213 | and on any theory of contract, warranty, tort (including 214 | negligence), product liability or otherwise, arising in any way 215 | in relation to the Covered Source, modified Covered Source 216 | and/or the Making or Conveyance of a Product, even if advised of 217 | the possibility of such damages, and You shall hold the 218 | Licensor(s) free and harmless from any liability, costs, 219 | damages, fees and expenses, including claims by third parties, 220 | in relation to such use. 221 | 222 | 223 | 7 Patents 224 | 225 | 7.1 Subject to the terms and conditions of this Licence, each 226 | Licensor hereby grants to You a perpetual, worldwide, 227 | non-exclusive, no-charge, royalty-free, irrevocable (except as 228 | stated in subsections 7.2 and 8.4) patent licence to Make, have 229 | Made, use, offer to sell, sell, import, and otherwise transfer 230 | the Covered Source and Products, where such licence applies only 231 | to those patent claims licensable by such Licensor that are 232 | necessarily infringed by exercising rights under the Covered 233 | Source as Conveyed by that Licensor. 234 | 235 | 7.2 If You institute patent litigation against any entity (including 236 | a cross-claim or counterclaim in a lawsuit) alleging that the 237 | Covered Source or a Product constitutes direct or contributory 238 | patent infringement, or You seek any declaration that a patent 239 | licensed to You under this Licence is invalid or unenforceable 240 | then any rights granted to You under this Licence shall 241 | terminate as of the date such process is initiated. 242 | 243 | 244 | 8 General 245 | 246 | 8.1 If any provisions of this Licence are or subsequently become 247 | invalid or unenforceable for any reason, the remaining 248 | provisions shall remain effective. 249 | 250 | 8.2 You shall not use any of the name (including acronyms and 251 | abbreviations), image, or logo by which the Licensor or CERN is 252 | known, except where needed to comply with section 3, or where 253 | the use is otherwise allowed by law. Any such permitted use 254 | shall be factual and shall not be made so as to suggest any kind 255 | of endorsement or implication of involvement by the Licensor or 256 | its personnel. 257 | 258 | 8.3 CERN may publish updated versions and variants of this Licence 259 | which it considers to be in the spirit of this version, but may 260 | differ in detail to address new problems or concerns. New 261 | versions will be published with a unique version number and a 262 | variant identifier specifying the variant. If the Licensor has 263 | specified that a given variant applies to the Covered Source 264 | without specifying a version, You may treat that Covered Source 265 | as being released under any version of the CERN-OHL with that 266 | variant. If no variant is specified, the Covered Source shall be 267 | treated as being released under CERN-OHL-S. The Licensor may 268 | also specify that the Covered Source is subject to a specific 269 | version of the CERN-OHL or any later version in which case You 270 | may apply this or any later version of CERN-OHL with the same 271 | variant identifier published by CERN. 272 | 273 | 8.4 This Licence shall terminate with immediate effect if You fail 274 | to comply with any of its terms and conditions. 275 | 276 | 8.5 However, if You cease all breaches of this Licence, then Your 277 | Licence from any Licensor is reinstated unless such Licensor has 278 | terminated this Licence by giving You, while You remain in 279 | breach, a notice specifying the breach and requiring You to cure 280 | it within 30 days, and You have failed to come into compliance 281 | in all material respects by the end of the 30 day period. Should 282 | You repeat the breach after receipt of a cure notice and 283 | subsequent reinstatement, this Licence will terminate 284 | immediately and permanently. Section 6 shall continue to apply 285 | after any termination. 286 | 287 | 8.6 This Licence shall not be enforceable except by a Licensor 288 | acting as such, and third party beneficiary rights are 289 | specifically excluded. 290 | -------------------------------------------------------------------------------- /hardware/boards/easyeda-top.json: -------------------------------------------------------------------------------- 1 | { 2 | "head": { 3 | "docType": "3", 4 | "editorVersion": "6.5.22", 5 | "newgId": true, 6 | "c_para": {}, 7 | "x": "4020", 8 | "y": "3385.5", 9 | "hasIdFlag": true, 10 | "importFlag": 0, 11 | "transformList": "" 12 | }, 13 | "canvas": "CA~1000~1000~#000000~yes~#FFFFFF~10~1000~1000~line~0.5~mm~1~45~visible~0.5~4020~3385.5~0~yes", 14 | "shape": [ 15 | "TRACK~1~10~~4076.5 3101.5 4393.5 3101.5 4393.5 3361~gge12094~0", 16 | "TRACK~1~10~~4393.5 3361 4076.5 3361~gge12091~0", 17 | "TRACK~1~10~~4076 3360.25 4076 3102~gge12088~0", 18 | "TRACK~1~3~S$15527~4340.5 3111.5 4340.5 3263~gge15526~0", 19 | "SVGNODE~{\"gId\":\"gge15423\",\"nodeName\":\"path\",\"nodeType\":1,\"layerid\":\"3\",\"attrs\":{\"d\":\"M 4373.5514 3111.5424 C 4373.1762 3111.7324 4372.9493 3111.9329 4372.742 3112.2574 L 4372.4539 3112.7083 L 4370.4715 3112.7135 C 4368.1358 3112.7196 4367.6001 3112.8343 4366.6776 3113.5254 C 4366.3689 3113.7566 4365.0792 3115.052 4363.8117 3116.4039 C 4362.1475 3118.1789 4361.387 3118.9217 4361.075 3119.0769 C 4360.6846 3119.2712 4360.5003 3119.2922 4359.1658 3119.2942 L 4357.6887 3119.2964 L 4357.5676 3118.8886 C 4356.9091 3116.6706 4354.1268 3115.6849 4352.1234 3116.9599 C 4350.9128 3117.7303 4350.2418 3119.2496 4350.5083 3120.6167 C 4351.0368 3123.3275 4354.0842 3124.5526 4356.2833 3122.9383 C 4356.834 3122.5341 4357.4833 3121.6172 4357.5786 3121.1093 L 4357.6395 3120.7848 L 4361.9259 3120.7891 C 4366.1556 3120.7934 4366.2176 3120.7962 4366.6134 3121.0051 C 4366.87 3121.1406 4367.8966 3122.1512 4369.4622 3123.8095 C 4372.7819 3127.3259 4372.7608 3127.3144 4375.8802 3127.3144 L 4377.7833 3127.3144 L 4377.7833 3128.0346 L 4377.7833 3128.7548 L 4379.9918 3128.7548 L 4382.2004 3128.7548 L 4382.2004 3126.5942 L 4382.2004 3124.4337 L 4379.9918 3124.4337 L 4377.7833 3124.4337 L 4377.7833 3125.1539 L 4377.7833 3125.8741 L 4375.9279 3125.8741 C 4374.4845 3125.8741 4373.9731 3125.8402 4373.6248 3125.7215 C 4373.2445 3125.5919 4372.9121 3125.2918 4371.4181 3123.729 C 4370.4507 3122.717 4369.4283 3121.6406 4369.1461 3121.3369 L 4368.6331 3120.7848 L 4377.0492 3120.7848 L 4385.4652 3120.7848 L 4385.4652 3121.657 C 4385.4652 3122.1367 4385.5017 3122.5148 4385.5462 3122.4972 C 4385.7675 3122.4097 4389.7854 3120.058 4389.7843 3120.0166 C 4389.7837 3119.9901 4388.8224 3119.4178 4387.6482 3118.7447 L 4385.5133 3117.5209 L 4385.4858 3118.4326 L 4385.4583 3119.3444 L 4374.3229 3119.3444 C 4368.1985 3119.3444 4363.1875 3119.311 4363.1875 3119.2701 C 4363.1875 3119.2293 4364.1922 3118.1408 4365.4201 3116.8513 C 4368.1505 3113.984 4367.7983 3114.1687 4370.5458 3114.1631 L 4372.4786 3114.1591 L 4372.5927 3114.4345 C 4372.7466 3114.8061 4373.2397 3115.2689 4373.6751 3115.4505 C 4374.1739 3115.6585 4375.0195 3115.6421 4375.4585 3115.4158 C 4376.8526 3114.6972 4377.1344 3113.0275 4376.046 3111.9345 C 4375.3339 3111.2193 4374.4604 3111.082 4373.5514 3111.5424\",\"id\":\"gge15423\",\"stroke\":\"none\",\"layerid\":\"3\"}}", 20 | "SVGNODE~{\"gId\":\"gge15448\",\"nodeName\":\"path\",\"nodeType\":1,\"layerid\":\"3\",\"attrs\":{\"d\":\"M 4355 3225 C 4353.6951 3225.69 4352.6324 3227.3739 4352.6324 3228.7515 C 4352.6324 3230.1291 4353.6951 3231.813 4355 3232.503 C 4357.0101 3233.566 4359.0758 3233.1567 4360.4606 3231.4212 L 4361.2203 3230.4691 L 4363.8051 3230.4691 C 4366.6844 3230.4691 4367.9161 3230.8295 4368.4069 3231.8155 C 4368.5808 3232.165 4368.7621 3233.3854 4368.8098 3234.5275 C 4368.8849 3236.3272 4368.9788 3236.6706 4369.5137 3237.1038 C 4370.2973 3237.7383 4370.9075 3237.7364 4371.6947 3237.0968 C 4372.2728 3236.6271 4372.3169 3236.4095 4372.297 3234.1241 C 4372.2735 3231.4142 4371.8519 3230.2067 4370.4348 3228.7896 C 4369.0402 3227.395 4368.3503 3227.1893 4364.7014 3227.0807 L 4361.3122 3226.9798 L 4360.1932 3225.8608 C 4358.5854 3224.253 4356.9311 3223.9788 4355 3225 M 4382.072 3236.5178 C 4381.6231 3236.7916 4381.0115 3237.3262 4380.7128 3237.706 C 4380.1893 3238.3715 4380.0703 3238.3964 4377.4151 3238.3964 C 4374.1826 3238.3964 4372.4733 3238.8273 4371.0557 3239.9998 C 4369.3846 3241.3819 4368.9091 3242.6115 4368.8952 3245.5863 C 4368.8818 3248.4824 4369.163 3249.0542 4370.6009 3249.0542 C 4371.9464 3249.0542 4372.2756 3248.4719 4372.4381 3245.8054 C 4372.6454 3242.4024 4372.8259 3242.2547 4376.9161 3242.1405 L 4380.0472 3242.053 L 4380.6506 3242.9004 C 4381.5163 3244.1162 4383.2836 3244.838 4384.8276 3244.6064 C 4386.4038 3244.3701 4388.1651 3242.7146 4388.4487 3241.203 C 4388.8073 3239.2916 4387.695 3237.081 4386.0232 3236.3825 C 4384.7931 3235.8685 4383.0383 3235.9286 4382.072 3236.5178 M 4354.849 3248.1328 C 4352.2893 3249.7415 4351.9664 3253.1103 4354.1803 3255.1104 C 4354.7461 3255.6215 4355.6196 3256.1065 4356.1556 3256.2071 C 4357.8996 3256.5342 4360.2961 3255.4976 4360.694 3254.244 C 4360.8466 3253.7633 4361.1054 3253.7224 4363.9979 3253.7224 C 4366.9331 3253.7224 4367.1843 3253.7637 4367.8777 3254.3593 C 4368.613 3254.991 4368.6202 3255.0273 4368.7512 3258.8244 C 4368.8643 3262.1026 4368.9556 3262.7324 4369.3867 3263.2081 C 4370.1061 3264.0021 4371.2265 3263.9267 4371.9292 3263.037 C 4372.4686 3262.3541 4372.4972 3262.0877 4372.405 3258.6109 C 4372.3199 3255.4015 4372.2258 3254.7453 4371.6944 3253.6561 C 4370.4104 3251.0241 4368.2418 3250.023 4363.8243 3250.023 L 4361.1073 3250.023 L 4360.0733 3248.9146 C 4359.0986 3247.8697 4358.9429 3247.8007 4357.3553 3247.7112 C 4356.0259 3247.6362 4355.498 3247.725 4354.849 3248.1328\",\"id\":\"gge15448\",\"stroke\":\"none\",\"layerid\":\"3\"}}", 21 | "SVGNODE~{\"gId\":\"gge15442\",\"nodeName\":\"path\",\"nodeType\":1,\"layerid\":\"3\",\"attrs\":{\"d\":\"M 4355 3154.5 C 4353.6951 3155.19 4352.6324 3156.8739 4352.6324 3158.2515 C 4352.6324 3159.6291 4353.6951 3161.313 4355 3162.003 C 4357.0101 3163.066 4359.0758 3162.6567 4360.4606 3160.9212 L 4361.2203 3159.9691 L 4363.8051 3159.9691 C 4366.6844 3159.9691 4367.9161 3160.3295 4368.4069 3161.3155 C 4368.5808 3161.665 4368.7621 3162.8854 4368.8098 3164.0275 C 4368.8849 3165.8272 4368.9788 3166.1706 4369.5137 3166.6038 C 4370.2973 3167.2383 4370.9075 3167.2364 4371.6947 3166.5968 C 4372.2728 3166.1271 4372.3169 3165.9095 4372.297 3163.6241 C 4372.2735 3160.9142 4371.8519 3159.7067 4370.4348 3158.2896 C 4369.0402 3156.895 4368.3503 3156.6893 4364.7014 3156.5807 L 4361.3122 3156.4798 L 4360.1932 3155.3608 C 4358.5854 3153.753 4356.9311 3153.4788 4355 3154.5 M 4382.072 3166.0178 C 4381.6231 3166.2916 4381.0115 3166.8262 4380.7128 3167.206 C 4380.1893 3167.8715 4380.0703 3167.8964 4377.4151 3167.8964 C 4374.1826 3167.8964 4372.4733 3168.3273 4371.0557 3169.4998 C 4369.3846 3170.8819 4368.9091 3172.1115 4368.8952 3175.0863 C 4368.8818 3177.9824 4369.163 3178.5542 4370.6009 3178.5542 C 4371.9464 3178.5542 4372.2756 3177.9719 4372.4381 3175.3054 C 4372.6454 3171.9024 4372.8259 3171.7547 4376.9161 3171.6405 L 4380.0472 3171.553 L 4380.6506 3172.4004 C 4381.5163 3173.6162 4383.2836 3174.338 4384.8276 3174.1064 C 4386.4038 3173.8701 4388.1651 3172.2146 4388.4487 3170.703 C 4388.8073 3168.7916 4387.695 3166.581 4386.0232 3165.8825 C 4384.7931 3165.3685 4383.0383 3165.4286 4382.072 3166.0178 M 4354.849 3177.6328 C 4352.2893 3179.2415 4351.9664 3182.6103 4354.1803 3184.6104 C 4354.7461 3185.1215 4355.6196 3185.6065 4356.1556 3185.7071 C 4357.8996 3186.0342 4360.2961 3184.9976 4360.694 3183.744 C 4360.8466 3183.2633 4361.1054 3183.2224 4363.9979 3183.2224 C 4366.9331 3183.2224 4367.1843 3183.2637 4367.8777 3183.8593 C 4368.613 3184.491 4368.6202 3184.5273 4368.7512 3188.3244 C 4368.8643 3191.6026 4368.9556 3192.2324 4369.3867 3192.7081 C 4370.1061 3193.5021 4371.2265 3193.4267 4371.9292 3192.537 C 4372.4686 3191.8541 4372.4972 3191.5877 4372.405 3188.1109 C 4372.3199 3184.9015 4372.2258 3184.2453 4371.6944 3183.1561 C 4370.4104 3180.5241 4368.2418 3179.523 4363.8243 3179.523 L 4361.1073 3179.523 L 4360.0733 3178.4146 C 4359.0986 3177.3697 4358.9429 3177.3007 4357.3553 3177.2112 C 4356.0259 3177.1362 4355.498 3177.225 4354.849 3177.6328\",\"id\":\"gge15442\",\"stroke\":\"none\",\"layerid\":\"3\"}}", 22 | "SVGNODE~{\"gId\":\"gge15463\",\"nodeName\":\"path\",\"nodeType\":1,\"layerid\":\"3\",\"attrs\":{\"d\":\"M 4358.3963 3305.7421 C 4355.8137 3308.3062 4353.6219 3310.7177 4353.5255 3311.101 C 4353.3755 3311.6976 4354.0472 3312.4957 4358.1969 3316.651 C 4362.9871 3321.4479 4363.771 3321.9969 4364.6189 3321.149 C 4365.4222 3320.3458 4364.9217 3319.5365 4361.5007 3316.1066 L 4358.0273 3312.6241 L 4372.9857 3312.6241 C 4384.2253 3312.6241 4388.0323 3312.5358 4388.2992 3312.2689 C 4388.7908 3311.7773 4388.7432 3310.9792 4388.1893 3310.4253 C 4387.7788 3310.0148 4385.9796 3309.9601 4372.8757 3309.9601 L 4358.0273 3309.9601 L 4361.5007 3306.4776 C 4365.07 3302.899 4365.5743 3302.014 4364.4016 3301.3864 C 4364.0867 3301.2179 4363.6632 3301.08 4363.4605 3301.08 C 4363.2577 3301.08 4360.9789 3303.1779 4358.3963 3305.7421 M 4377.4655 3320.3794 C 4376.6553 3321.1896 4377.1523 3321.9998 4380.5193 3325.3571 L 4383.9283 3328.7563 L 4368.9333 3328.9043 C 4355.3382 3329.0385 4353.9092 3329.0985 4353.6262 3329.5473 C 4353.4042 3329.8994 4353.4144 3330.2628 4353.6617 3330.8054 L 4354.0093 3331.5684 L 4369.0332 3331.5684 L 4384.0571 3331.5684 L 4380.5837 3335.0508 C 4378.4286 3337.2116 4377.1103 3338.7619 4377.1103 3339.1357 C 4377.1103 3339.8996 4377.6525 3340.4485 4378.4072 3340.4485 C 4379.2211 3340.4485 4388.6544 3331.0551 4388.6544 3330.2447 C 4388.6544 3329.4176 4379.252 3320.0242 4378.4242 3320.0242 C 4378.0923 3320.0242 4377.6608 3320.1841 4377.4655 3320.3794\",\"id\":\"gge15463\",\"stroke\":\"none\",\"layerid\":\"3\"}}", 23 | "TEXT~L~4333~3217.5~0.8~90~0~3~~8~Power Only~M 4324.931 3217.501 L 4332.561 3217.501 M 4324.931 3217.501 L 4324.931 3214.231 L 4325.291 3213.141 L 4325.651 3212.771 L 4326.381 3212.411 L 4327.471 3212.411 L 4328.201 3212.771 L 4328.561 3213.141 L 4328.931 3214.231 L 4328.931 3217.501 M 4327.471 3208.191 L 4327.841 3208.921 L 4328.561 3209.641 L 4329.651 3210.011 L 4330.381 3210.011 L 4331.471 3209.641 L 4332.201 3208.921 L 4332.561 3208.191 L 4332.561 3207.101 L 4332.201 3206.371 L 4331.471 3205.641 L 4330.381 3205.281 L 4329.651 3205.281 L 4328.561 3205.641 L 4327.841 3206.371 L 4327.471 3207.101 L 4327.471 3208.191 M 4327.471 3202.881 L 4332.561 3201.431 M 4327.471 3199.971 L 4332.561 3201.431 M 4327.471 3199.971 L 4332.561 3198.521 M 4327.471 3197.061 L 4332.561 3198.521 M 4329.651 3194.661 L 4329.651 3190.301 L 4328.931 3190.301 L 4328.201 3190.661 L 4327.841 3191.031 L 4327.471 3191.751 L 4327.471 3192.841 L 4327.841 3193.571 L 4328.561 3194.301 L 4329.651 3194.661 L 4330.381 3194.661 L 4331.471 3194.301 L 4332.201 3193.571 L 4332.561 3192.841 L 4332.561 3191.751 L 4332.201 3191.031 L 4331.471 3190.301 M 4327.471 3187.901 L 4332.561 3187.901 M 4329.651 3187.901 L 4328.561 3187.541 L 4327.841 3186.811 L 4327.471 3186.081 L 4327.471 3184.991 M 4324.931 3174.811 L 4325.291 3175.541 L 4326.021 3176.261 L 4326.741 3176.631 L 4327.841 3176.991 L 4329.651 3176.991 L 4330.741 3176.631 L 4331.471 3176.261 L 4332.201 3175.541 L 4332.561 3174.811 L 4332.561 3173.351 L 4332.201 3172.631 L 4331.471 3171.901 L 4330.741 3171.541 L 4329.651 3171.171 L 4327.841 3171.171 L 4326.741 3171.541 L 4326.021 3171.901 L 4325.291 3172.631 L 4324.931 3173.351 L 4324.931 3174.811 M 4327.471 3168.771 L 4332.561 3168.771 M 4328.931 3168.771 L 4327.841 3167.681 L 4327.471 3166.951 L 4327.471 3165.861 L 4327.841 3165.141 L 4328.931 3164.771 L 4332.561 3164.771 M 4324.931 3162.371 L 4332.561 3162.371 M 4327.471 3159.611 L 4332.561 3157.431 M 4327.471 3155.241 L 4332.561 3157.431 L 4334.021 3158.151 L 4334.741 3158.881 L 4335.111 3159.611 L 4335.111 3159.971~~gge15514~~0~pinpart", 24 | "SVGNODE~{\"gId\":\"gge15539\",\"nodeName\":\"path\",\"nodeType\":1,\"layerid\":\"3\",\"attrs\":{\"d\":\"M 4238.5 3116.5 C 4237.9761 3116.8661 4236.1507 3117.3729 4234.4436 3117.6263 C 4232.3206 3117.9414 4231.3398 3118.2781 4231.3398 3118.6918 C 4231.3398 3119.0618 4231.8164 3119.2966 4232.5677 3119.2966 C 4233.9952 3119.2966 4234.848 3120.0015 4234.848 3121.1816 C 4234.848 3121.6395 4232.7617 3129.5429 4230.2116 3138.7446 C 4224.544 3159.1961 4223.8918 3161.8796 4223.8879 3164.7626 C 4223.8844 3167.3619 4224.6242 3168.4117 4226.4596 3168.4117 C 4228.97 3168.4117 4232.3643 3165.2575 4235.0772 3160.4035 C 4236.7243 3157.4566 4236.2884 3156.1242 4234.602 3158.951 C 4231.5741 3164.0264 4228.7087 3165.9952 4228.7087 3163.0001 C 4228.7087 3162.1945 4231.3727 3151.4517 4234.6288 3139.1272 C 4237.8849 3126.8028 4240.5489 3116.5097 4240.5489 3116.2537 C 4240.5489 3115.6022 4239.6261 3115.7131 4238.5 3116.5 M 4254.1433 3116.676 C 4253.0579 3117.1161 4251.1979 3117.6391 4250.0099 3117.8382 C 4247.5641 3118.2482 4246.7037 3119.2966 4248.8131 3119.2966 C 4250.8331 3119.2966 4251.4761 3120.205 4251.0485 3122.4546 C 4250.8514 3123.4914 4248.7282 3131.8385 4246.3303 3141.0037 C 4238.993 3169.0478 4239.4419 3167.1204 4240.1442 3167.5649 C 4240.9144 3168.0523 4242.3394 3168.0759 4243.0678 3167.6133 C 4243.3679 3167.4228 4244.2009 3165.1747 4244.9189 3162.6176 C 4246.5449 3156.8269 4247.5119 3154.3788 4248.1732 3154.3788 C 4249.4941 3154.3788 4250.5924 3155.9106 4251.9074 3159.587 C 4254.3239 3166.3425 4256.8776 3168.7613 4261.0915 3168.2864 C 4264.8743 3167.86 4267.4352 3163.5843 4265.993 3160.1026 C 4265.375 3158.6105 4264.0501 3158.003 4263.1121 3158.7816 C 4262.2055 3159.5339 4262.3185 3160.2646 4263.5716 3161.7538 C 4264.1746 3162.4704 4264.668 3163.4284 4264.668 3163.8826 C 4264.668 3165.1799 4263.4019 3166.6576 4262.2903 3166.6576 C 4260.4371 3166.6576 4259.1305 3165.063 4257.1774 3160.4177 C 4256.1359 3157.9405 4254.7463 3155.371 4254.0894 3154.7076 L 4252.895 3153.5016 L 4254.0673 3153.2571 C 4259.9718 3152.0257 4262.9151 3150.781 4265.559 3148.3974 C 4270.1884 3144.2238 4270.9138 3136.4513 4266.8955 3134.0776 C 4262.448 3131.4504 4256.2503 3135.9233 4250.1845 3146.1381 C 4249.3669 3147.5148 4248.6355 3148.5787 4248.5591 3148.5023 C 4248.4827 3148.4259 4250.3986 3141.1231 4252.8166 3132.2739 C 4255.2346 3123.4246 4257.213 3116.0952 4257.213 3115.9863 C 4257.213 3115.6252 4256.1404 3115.8662 4254.1433 3116.676 M 4170.2097 3120.2833 C 4169.8383 3121.5495 4169.0611 3124.1643 4168.4827 3126.0938 C 4166.932 3131.2671 4167.7913 3132.3882 4170.0125 3128.0896 C 4172.3095 3123.6444 4173.8121 3122.8116 4179.5435 3122.8073 L 4182.7824 3122.8048 L 4182.518 3124.6686 C 4182.3725 3125.6936 4180.0793 3135.0178 4177.4219 3145.389 C 4173.7751 3159.6217 4172.3799 3164.4192 4171.7323 3164.953 C 4171.2604 3165.342 4170.0598 3165.7905 4169.0643 3165.9497 C 4167.6626 3166.1738 4167.2885 3166.41 4167.4062 3166.9965 C 4167.5143 3167.5358 4167.9335 3167.729 4168.8616 3167.6675 C 4169.5786 3167.6199 4172.8292 3167.5212 4176.0853 3167.4482 C 4180.824 3167.3419 4182.0054 3167.1921 4182.0054 3166.6972 C 4182.0054 3166.3532 4181.2484 3165.8666 4180.2986 3165.6001 C 4179.3599 3165.3367 4178.4118 3164.8256 4178.1918 3164.4642 C 4177.9204 3164.0184 4179.3511 3157.5334 4182.6419 3144.2927 C 4185.3095 3133.5597 4187.684 3124.3342 4187.9187 3123.7915 C 4188.3133 3122.8788 4188.6256 3122.8048 4192.0822 3122.8048 C 4197.0162 3122.8048 4197.5732 3123.2941 4197.5732 3127.6286 C 4197.5732 3130.087 4197.7063 3130.6983 4198.2418 3130.6983 C 4199.0459 3130.6983 4199.2631 3130.1323 4200.8848 3123.8119 C 4202.1492 3118.884 4202.0979 3117.4098 4200.7225 3119.1422 C 4199.2803 3120.9586 4198.5469 3121.0507 4185.5201 3121.0507 L 4172.9708 3121.0507 L 4172.3295 3119.5159 C 4171.9768 3118.6717 4171.5075 3117.981 4171.2867 3117.981 C 4171.0659 3117.981 4170.5812 3119.017 4170.2097 3120.2833 M 4110.2976 3120.7218 C 4110.0621 3121.2645 4107.8783 3128.418 4105.4448 3136.6184 C 4098.8505 3158.8402 4097.3493 3163.5182 4096.5174 3164.4374 C 4096.1019 3164.8965 4095.0383 3165.4432 4094.1538 3165.6524 C 4093.2084 3165.876 4092.5457 3166.297 4092.5457 3166.6741 C 4092.5457 3167.1956 4093.4058 3167.3169 4097.1502 3167.3235 C 4099.6827 3167.3279 4102.3468 3167.4756 4103.0704 3167.6516 C 4103.7939 3167.8276 4104.6614 3167.7946 4104.998 3167.5782 C 4106.0178 3166.9226 4105.1915 3165.8473 4103.4223 3165.5278 C 4099.7627 3164.8668 4099.5609 3164.1549 4101.2809 3157.9729 C 4101.8645 3155.8756 4102.5642 3153.3157 4102.8358 3152.2842 L 4103.3297 3150.4089 L 4109.7354 3150.5301 L 4116.1412 3150.6513 L 4117.3967 3154.5981 C 4120.3661 3163.9327 4120.3891 3165.342 4117.5719 3165.342 C 4116.0471 3165.342 4114.4083 3166.2835 4114.8079 3166.9299 C 4114.95 3167.1599 4120.954 3167.3841 4128.6885 3167.4482 C 4140.681 3167.5477 4142.3186 3167.4806 4142.3186 3166.8902 C 4142.3186 3166.5211 4142.0128 3166.2191 4141.639 3166.2191 C 4141.2653 3166.2191 4140.3773 3165.918 4139.6657 3165.55 L 4138.3719 3164.881 L 4138.3719 3149.5438 C 4138.3719 3132.7687 4138.4333 3133.1219 4135.884 3135.2331 C 4135.2022 3135.7977 4133.6985 3136.8081 4132.5424 3137.4785 C 4131.3863 3138.1489 4130.5071 3138.8972 4130.5885 3139.1415 C 4130.6699 3139.3858 4131.4185 3139.806 4132.2519 3140.0754 L 4133.7673 3140.5652 L 4133.8839 3152.7538 L 4134.0005 3164.9424 L 4132.486 3165.5753 C 4130.8474 3166.2599 4128.6919 3166.0624 4127.1165 3165.0833 C 4126.594 3164.7586 4125.7392 3163.3519 4125.2169 3161.9574 C 4124.3204 3159.5639 4114.2869 3127.6771 4112.7247 3122.2567 C 4111.9494 3119.5666 4111.0467 3118.9957 4110.2976 3120.7218 M 4134.2307 3122.0253 C 4133.3544 3123.2765 4133.3544 3125.8414 4134.2307 3127.0925 C 4135.7405 3129.2481 4138.5712 3127.5958 4138.5712 3124.5589 C 4138.5712 3121.5221 4135.7405 3119.8698 4134.2307 3122.0253 M 4112.4327 3138.9908 C 4113.897 3143.4311 4114.9925 3147.2299 4114.8673 3147.4325 C 4114.742 3147.6351 4112.3818 3147.8009 4109.6224 3147.8009 C 4105.5601 3147.8009 4104.6052 3147.6814 4104.6052 3147.1727 C 4104.6052 3146.4241 4108.8328 3131.7467 4109.2367 3131.0933 C 4109.3908 3130.844 4109.5739 3130.7024 4109.6437 3130.7788 C 4109.7134 3130.8551 4110.9685 3134.5505 4112.4327 3138.9908 M 4216.4839 3134.0091 C 4215.1747 3135.1107 4215.1337 3135.116 4213.7095 3134.3652 C 4209.8005 3132.3045 4203.9117 3134.37 4198.6377 3139.6518 C 4191.9276 3146.3717 4188.2208 3153.8549 4188.1927 3160.7375 C 4188.1762 3164.7977 4188.9247 3166.6487 4191.1262 3167.991 C 4196.1531 3171.0561 4201.339 3166.6366 4211.0209 3151.0365 C 4211.3021 3150.5835 4210.7601 3153.1729 4209.8166 3156.7907 C 4208.0753 3163.4675 4207.6716 3167.3716 4208.6241 3168.324 C 4208.9135 3168.6135 4209.8424 3168.8503 4210.6882 3168.8503 C 4211.9988 3168.8503 4212.6192 3168.4451 4214.8848 3166.1095 C 4217.6062 3163.304 4220.4183 3158.6587 4219.7802 3158.0227 C 4219.4248 3157.6685 4219.1064 3158.0115 4216.6775 3161.3648 C 4214.5755 3164.2667 4212.6012 3164.1602 4213.177 3161.176 C 4213.3167 3160.4525 4215.0152 3154.0895 4216.9517 3147.036 C 4218.8881 3139.9826 4220.3584 3133.9145 4220.219 3133.5513 C 4219.8298 3132.5371 4217.9611 3132.7661 4216.4839 3134.0091 M 4150.8423 3135.1227 C 4150.1339 3135.8678 4148.7156 3136.9719 4147.6906 3137.5763 C 4145.6923 3138.7545 4145.308 3139.4689 4146.6726 3139.4689 C 4148.8012 3139.4689 4148.8965 3140.033 4148.8965 3152.6247 C 4148.8965 3165.5529 4148.8994 3165.5388 4146.2125 3166.0762 C 4145.518 3166.2151 4144.9498 3166.5899 4144.9498 3166.9092 C 4144.9498 3167.358 4146.393 3167.5196 4151.3153 3167.6218 C 4154.8163 3167.6945 4157.875 3167.6882 4158.1124 3167.6078 C 4158.8566 3167.3558 4158.5823 3166.2192 4157.7767 3166.2167 C 4157.3546 3166.2153 4156.2748 3166.0102 4155.3771 3165.7609 C 4152.978 3165.0946 4152.6867 3163.5589 4152.9692 3153.0633 C 4153.2194 3143.7685 4153.7576 3140.9416 4155.6698 3138.8783 C 4157.0844 3137.3519 4158.5936 3137.3999 4160.4505 3139.0304 C 4162.4198 3140.7594 4164.0047 3140.7672 4164.8916 3139.0521 C 4165.8452 3137.2081 4165.7238 3136.084 4164.4316 3134.7918 C 4163.4485 3133.8087 4163.0256 3133.6893 4161.1611 3133.8685 C 4158.5941 3134.1151 4156.5604 3135.5277 4154.4998 3138.4953 L 4153.0626 3140.5652 L 4152.9345 3137.1666 C 4152.864 3135.2974 4152.6543 3133.768 4152.4684 3133.768 C 4152.2824 3133.768 4151.5507 3134.3776 4150.8423 3135.1227 M 4212.9105 3135.9495 C 4214.0156 3137.0546 4214.4131 3138.6536 4214.0852 3140.6743 C 4213.39 3144.9584 4205.4177 3157.9106 4200.7238 3162.382 C 4198.4243 3164.5725 4197.8512 3164.9035 4196.3581 3164.9035 C 4191.7885 3164.9035 4191.6654 3159.1874 4196.0387 3150.078 C 4200.539 3140.7041 4205.8593 3135.1565 4210.4001 3135.103 C 4211.4726 3135.0903 4212.3458 3135.3848 4212.9105 3135.9495 M 4263.951 3137.3081 C 4264.8739 3138.034 4265.1045 3138.5857 4265.0969 3140.0489 C 4265.0663 3145.8661 4258.6272 3151.7345 4251.8549 3152.1171 C 4250.2192 3152.2095 4248.8809 3152.1586 4248.8809 3152.0039 C 4248.8809 3151.8492 4249.4992 3150.4949 4250.2548 3148.9944 C 4252.8988 3143.7438 4256.1497 3139.7001 4259.3818 3137.6411 C 4261.7077 3136.1595 4262.4244 3136.1072 4263.951 3137.3081\",\"id\":\"gge15539\",\"stroke\":\"none\",\"layerid\":\"3\"}}", 25 | "HOLE~4091.717~3343.37~6.8898~gge12103~0", 26 | "HOLE~4091.717~3118.961~6.8898~gge12106~0", 27 | "HOLE~4312.189~3118.961~6.8898~gge12121~0", 28 | "HOLE~4312.189~3343.37~6.8898~gge12136~0", 29 | "SOLIDREGION~11~~M4104.756,3270L4104.756,3363.5L4253.756,3363.5L4253.756,3270Z~npth~gge14552~~~~0", 30 | "PAD~ELLIPSE~4387~3355~6~6~11~~1~1.8~~0~gge15552~0~~Y~0~~~4387,3355" 31 | ], 32 | "layers": [ 33 | "1~TopLayer~#FF0000~true~true~true~", 34 | "2~BottomLayer~#0000FF~true~false~true~", 35 | "3~TopSilkLayer~#FFCC00~true~false~true~", 36 | "4~BottomSilkLayer~#66CC33~true~false~true~", 37 | "5~TopPasteMaskLayer~#808080~true~false~true~", 38 | "6~BottomPasteMaskLayer~#800000~true~false~true~", 39 | "7~TopSolderMaskLayer~#800080~true~false~true~0.3", 40 | "8~BottomSolderMaskLayer~#AA00FF~true~false~true~0.3", 41 | "9~Ratlines~#6464FF~true~false~true~", 42 | "10~BoardOutLine~#FF00FF~true~false~true~", 43 | "11~Multi-Layer~#C0C0C0~true~false~true~", 44 | "12~Document~#FFFFFF~true~false~true~", 45 | "13~TopAssembly~#33CC99~false~false~false~", 46 | "14~BottomAssembly~#5555FF~false~false~false~", 47 | "15~Mechanical~#F022F0~false~false~false~", 48 | "19~3DModel~#66CCFF~false~false~false~", 49 | "21~Inner1~#999966~false~false~false~~", 50 | "22~Inner2~#008000~false~false~false~~", 51 | "23~Inner3~#00FF00~false~false~false~~", 52 | "24~Inner4~#BC8E00~false~false~false~~", 53 | "25~Inner5~#70DBFA~false~false~false~~", 54 | "26~Inner6~#00CC66~false~false~false~~", 55 | "27~Inner7~#9966FF~false~false~false~~", 56 | "28~Inner8~#800080~false~false~false~~", 57 | "29~Inner9~#008080~false~false~false~~", 58 | "30~Inner10~#15935F~false~false~false~~", 59 | "31~Inner11~#000080~false~false~false~~", 60 | "32~Inner12~#00B400~false~false~false~~", 61 | "33~Inner13~#2E4756~false~false~false~~", 62 | "34~Inner14~#99842F~false~false~false~~", 63 | "35~Inner15~#FFFFAA~false~false~false~~", 64 | "36~Inner16~#99842F~false~false~false~~", 65 | "37~Inner17~#2E4756~false~false~false~~", 66 | "38~Inner18~#3535FF~false~false~false~~", 67 | "39~Inner19~#8000BC~false~false~false~~", 68 | "40~Inner20~#43AE5F~false~false~false~~", 69 | "41~Inner21~#C3ECCE~false~false~false~~", 70 | "42~Inner22~#728978~false~false~false~~", 71 | "43~Inner23~#39503F~false~false~false~~", 72 | "44~Inner24~#0C715D~false~false~false~~", 73 | "45~Inner25~#5A8A80~false~false~false~~", 74 | "46~Inner26~#2B937E~false~false~false~~", 75 | "47~Inner27~#23999D~false~false~false~~", 76 | "48~Inner28~#45B4E3~false~false~false~~", 77 | "49~Inner29~#215DA1~false~false~false~~", 78 | "50~Inner30~#4564D7~false~false~false~~", 79 | "51~Inner31~#6969E9~false~false~false~~", 80 | "52~Inner32~#9069E9~false~false~false~~", 81 | "99~ComponentShapeLayer~#00CCCC~false~false~false~0.4", 82 | "100~LeadShapeLayer~#CC9999~false~false~false~", 83 | "101~ComponentMarkingLayer~#66FFCC~false~false~false~", 84 | "Hole~Hole~#222222~false~false~true~", 85 | "DRCError~DRCError~#FAD609~false~false~true~" 86 | ], 87 | "objects": [ 88 | "All~true~false", 89 | "Component~true~true", 90 | "Prefix~true~true", 91 | "Name~true~false", 92 | "Track~true~true", 93 | "Pad~true~true", 94 | "Via~true~true", 95 | "Hole~true~true", 96 | "Copper_Area~true~true", 97 | "Circle~true~true", 98 | "Arc~true~true", 99 | "Solid_Region~true~true", 100 | "Text~true~true", 101 | "Image~true~true", 102 | "Rect~true~true", 103 | "Dimension~true~true", 104 | "Protractor~true~true" 105 | ], 106 | "BBox": { 107 | "x": 4076, 108 | "y": 3101.5, 109 | "width": 317.5, 110 | "height": 262 111 | }, 112 | "preference": { 113 | "hideFootprints": "", 114 | "hideNets": "" 115 | }, 116 | "DRCRULE": { 117 | "Default": { 118 | "trackWidth": 1, 119 | "clearance": 0.6, 120 | "viaHoleDiameter": 2.4, 121 | "viaHoleD": 1.2 122 | }, 123 | "isRealtime": false, 124 | "isDrcOnRoutingOrPlaceVia": false, 125 | "checkObjectToCopperarea": true, 126 | "showDRCRangeLine": true 127 | }, 128 | "netColors": [] 129 | } -------------------------------------------------------------------------------- /hardware/boards/easyeda-bottom.json: -------------------------------------------------------------------------------- 1 | { 2 | "head": { 3 | "docType": "3", 4 | "editorVersion": "6.5.22", 5 | "newgId": true, 6 | "c_para": {}, 7 | "x": "4020", 8 | "y": "3385.5", 9 | "hasIdFlag": true, 10 | "importFlag": 0, 11 | "transformList": "" 12 | }, 13 | "canvas": "CA~1000~1000~#000000~yes~#FFFFFF~10~1000~1000~line~0.5~mil~1~45~visible~0.5~4020~3385.5~0~yes", 14 | "shape": [ 15 | "TRACK~1~10~~4076.5 3101.5 4393.5 3101.5 4393.5 3361~gge12094~0", 16 | "TRACK~1~10~~4393.5 3361 4076.5 3361~gge12091~0", 17 | "TRACK~1~10~~4076 3360.25 4076 3102~gge12088~0", 18 | "TEXT~L~4293.5~3354.5~0.8~0~1~2~~8~cheesey me fecit~M 4289.1364 3350.0636 L 4289.8636 3349.3364 L 4290.5909 3348.9727 L 4291.6818 3348.9727 L 4292.4091 3349.3364 L 4293.1364 3350.0636 L 4293.5 3351.1545 L 4293.5 3351.8818 L 4293.1364 3352.9727 L 4292.4091 3353.7 L 4291.6818 3354.0636 L 4290.5909 3354.0636 L 4289.8636 3353.7 L 4289.1364 3352.9727 M 4286.7364 3346.4273 L 4286.7364 3354.0636 M 4286.7364 3350.4273 L 4285.6455 3349.3364 L 4284.9182 3348.9727 L 4283.8273 3348.9727 L 4283.1 3349.3364 L 4282.7364 3350.4273 L 4282.7364 3354.0636 M 4280.3364 3351.1545 L 4275.9727 3351.1545 L 4275.9727 3350.4273 L 4276.3364 3349.7 L 4276.7 3349.3364 L 4277.4273 3348.9727 L 4278.5182 3348.9727 L 4279.2455 3349.3364 L 4279.9727 3350.0636 L 4280.3364 3351.1545 L 4280.3364 3351.8818 L 4279.9727 3352.9727 L 4279.2455 3353.7 L 4278.5182 3354.0636 L 4277.4273 3354.0636 L 4276.7 3353.7 L 4275.9727 3352.9727 M 4273.5727 3351.1545 L 4269.2091 3351.1545 L 4269.2091 3350.4273 L 4269.5727 3349.7 L 4269.9364 3349.3364 L 4270.6636 3348.9727 L 4271.7545 3348.9727 L 4272.4818 3349.3364 L 4273.2091 3350.0636 L 4273.5727 3351.1545 L 4273.5727 3351.8818 L 4273.2091 3352.9727 L 4272.4818 3353.7 L 4271.7545 3354.0636 L 4270.6636 3354.0636 L 4269.9364 3353.7 L 4269.2091 3352.9727 M 4262.8091 3350.0636 L 4263.1727 3349.3364 L 4264.2636 3348.9727 L 4265.3545 3348.9727 L 4266.4455 3349.3364 L 4266.8091 3350.0636 L 4266.4455 3350.7909 L 4265.7182 3351.1545 L 4263.9 3351.5182 L 4263.1727 3351.8818 L 4262.8091 3352.6091 L 4262.8091 3352.9727 L 4263.1727 3353.7 L 4264.2636 3354.0636 L 4265.3545 3354.0636 L 4266.4455 3353.7 L 4266.8091 3352.9727 M 4260.4091 3351.1545 L 4256.0455 3351.1545 L 4256.0455 3350.4273 L 4256.4091 3349.7 L 4256.7727 3349.3364 L 4257.5 3348.9727 L 4258.5909 3348.9727 L 4259.3182 3349.3364 L 4260.0455 3350.0636 L 4260.4091 3351.1545 L 4260.4091 3351.8818 L 4260.0455 3352.9727 L 4259.3182 3353.7 L 4258.5909 3354.0636 L 4257.5 3354.0636 L 4256.7727 3353.7 L 4256.0455 3352.9727 M 4253.2818 3348.9727 L 4251.1 3354.0636 M 4248.9182 3348.9727 L 4251.1 3354.0636 L 4251.8273 3355.5182 L 4252.5545 3356.2455 L 4253.2818 3356.6091 L 4253.6455 3356.6091 M 4240.9182 3348.9727 L 4240.9182 3354.0636 M 4240.9182 3350.4273 L 4239.8273 3349.3364 L 4239.1 3348.9727 L 4238.0091 3348.9727 L 4237.2818 3349.3364 L 4236.9182 3350.4273 L 4236.9182 3354.0636 M 4236.9182 3350.4273 L 4235.8273 3349.3364 L 4235.1 3348.9727 L 4234.0091 3348.9727 L 4233.2818 3349.3364 L 4232.9182 3350.4273 L 4232.9182 3354.0636 M 4230.5182 3351.1545 L 4226.1545 3351.1545 L 4226.1545 3350.4273 L 4226.5182 3349.7 L 4226.8818 3349.3364 L 4227.6091 3348.9727 L 4228.7 3348.9727 L 4229.4273 3349.3364 L 4230.1545 3350.0636 L 4230.5182 3351.1545 L 4230.5182 3351.8818 L 4230.1545 3352.9727 L 4229.4273 3353.7 L 4228.7 3354.0636 L 4227.6091 3354.0636 L 4226.8818 3353.7 L 4226.1545 3352.9727 M 4215.2455 3346.4273 L 4215.9727 3346.4273 L 4216.7 3346.7909 L 4217.0636 3347.8818 L 4217.0636 3354.0636 M 4218.1545 3348.9727 L 4215.6091 3348.9727 M 4212.8455 3351.1545 L 4208.4818 3351.1545 L 4208.4818 3350.4273 L 4208.8455 3349.7 L 4209.2091 3349.3364 L 4209.9364 3348.9727 L 4211.0273 3348.9727 L 4211.7545 3349.3364 L 4212.4818 3350.0636 L 4212.8455 3351.1545 L 4212.8455 3351.8818 L 4212.4818 3352.9727 L 4211.7545 3353.7 L 4211.0273 3354.0636 L 4209.9364 3354.0636 L 4209.2091 3353.7 L 4208.4818 3352.9727 M 4201.7182 3350.0636 L 4202.4455 3349.3364 L 4203.1727 3348.9727 L 4204.2636 3348.9727 L 4204.9909 3349.3364 L 4205.7182 3350.0636 L 4206.0818 3351.1545 L 4206.0818 3351.8818 L 4205.7182 3352.9727 L 4204.9909 3353.7 L 4204.2636 3354.0636 L 4203.1727 3354.0636 L 4202.4455 3353.7 L 4201.7182 3352.9727 M 4199.3182 3346.4273 L 4198.9545 3346.7909 L 4198.5909 3346.4273 L 4198.9545 3346.0636 L 4199.3182 3346.4273 M 4198.9545 3348.9727 L 4198.9545 3354.0636 M 4195.1 3346.4273 L 4195.1 3352.6091 L 4194.7364 3353.7 L 4194.0091 3354.0636 L 4193.2818 3354.0636 M 4196.1909 3348.9727 L 4193.6455 3348.9727~~gge12146~~0~pinpart", 19 | "TEXT~L~4312~3212.5~0.8~0~1~4~~8~special thanks to \\nTashtari, demik, Phipli, \\ntechfury, LaPorta & all the \\nencouraging folk at the 68kMLA.~M 4308 3208.0636 L 4308.3636 3207.3364 L 4309.4545 3206.9727 L 4310.5455 3206.9727 L 4311.6364 3207.3364 L 4312 3208.0636 L 4311.6364 3208.7909 L 4310.9091 3209.1545 L 4309.0909 3209.5182 L 4308.3636 3209.8818 L 4308 3210.6091 L 4308 3210.9727 L 4308.3636 3211.7 L 4309.4545 3212.0636 L 4310.5455 3212.0636 L 4311.6364 3211.7 L 4312 3210.9727 M 4305.6 3206.9727 L 4305.6 3214.6091 M 4305.6 3208.0636 L 4304.8727 3207.3364 L 4304.1455 3206.9727 L 4303.0545 3206.9727 L 4302.3273 3207.3364 L 4301.6 3208.0636 L 4301.2364 3209.1545 L 4301.2364 3209.8818 L 4301.6 3210.9727 L 4302.3273 3211.7 L 4303.0545 3212.0636 L 4304.1455 3212.0636 L 4304.8727 3211.7 L 4305.6 3210.9727 M 4298.8364 3209.1545 L 4294.4727 3209.1545 L 4294.4727 3208.4273 L 4294.8364 3207.7 L 4295.2 3207.3364 L 4295.9273 3206.9727 L 4297.0182 3206.9727 L 4297.7455 3207.3364 L 4298.4727 3208.0636 L 4298.8364 3209.1545 L 4298.8364 3209.8818 L 4298.4727 3210.9727 L 4297.7455 3211.7 L 4297.0182 3212.0636 L 4295.9273 3212.0636 L 4295.2 3211.7 L 4294.4727 3210.9727 M 4287.7091 3208.0636 L 4288.4364 3207.3364 L 4289.1636 3206.9727 L 4290.2545 3206.9727 L 4290.9818 3207.3364 L 4291.7091 3208.0636 L 4292.0727 3209.1545 L 4292.0727 3209.8818 L 4291.7091 3210.9727 L 4290.9818 3211.7 L 4290.2545 3212.0636 L 4289.1636 3212.0636 L 4288.4364 3211.7 L 4287.7091 3210.9727 M 4285.3091 3204.4273 L 4284.9455 3204.7909 L 4284.5818 3204.4273 L 4284.9455 3204.0636 L 4285.3091 3204.4273 M 4284.9455 3206.9727 L 4284.9455 3212.0636 M 4277.8182 3206.9727 L 4277.8182 3212.0636 M 4277.8182 3208.0636 L 4278.5455 3207.3364 L 4279.2727 3206.9727 L 4280.3636 3206.9727 L 4281.0909 3207.3364 L 4281.8182 3208.0636 L 4282.1818 3209.1545 L 4282.1818 3209.8818 L 4281.8182 3210.9727 L 4281.0909 3211.7 L 4280.3636 3212.0636 L 4279.2727 3212.0636 L 4278.5455 3211.7 L 4277.8182 3210.9727 M 4275.4182 3204.4273 L 4275.4182 3212.0636 M 4266.3273 3204.4273 L 4266.3273 3210.6091 L 4265.9636 3211.7 L 4265.2364 3212.0636 L 4264.5091 3212.0636 M 4267.4182 3206.9727 L 4264.8727 3206.9727 M 4262.1091 3204.4273 L 4262.1091 3212.0636 M 4262.1091 3208.4273 L 4261.0182 3207.3364 L 4260.2909 3206.9727 L 4259.2 3206.9727 L 4258.4727 3207.3364 L 4258.1091 3208.4273 L 4258.1091 3212.0636 M 4251.3455 3206.9727 L 4251.3455 3212.0636 M 4251.3455 3208.0636 L 4252.0727 3207.3364 L 4252.8 3206.9727 L 4253.8909 3206.9727 L 4254.6182 3207.3364 L 4255.3455 3208.0636 L 4255.7091 3209.1545 L 4255.7091 3209.8818 L 4255.3455 3210.9727 L 4254.6182 3211.7 L 4253.8909 3212.0636 L 4252.8 3212.0636 L 4252.0727 3211.7 L 4251.3455 3210.9727 M 4248.9455 3206.9727 L 4248.9455 3212.0636 M 4248.9455 3208.4273 L 4247.8545 3207.3364 L 4247.1273 3206.9727 L 4246.0364 3206.9727 L 4245.3091 3207.3364 L 4244.9455 3208.4273 L 4244.9455 3212.0636 M 4242.5455 3204.4273 L 4242.5455 3212.0636 M 4238.9091 3206.9727 L 4242.5455 3210.6091 M 4241.0909 3209.1545 L 4238.5455 3212.0636 M 4232.1455 3208.0636 L 4232.5091 3207.3364 L 4233.6 3206.9727 L 4234.6909 3206.9727 L 4235.7818 3207.3364 L 4236.1455 3208.0636 L 4235.7818 3208.7909 L 4235.0545 3209.1545 L 4233.2364 3209.5182 L 4232.5091 3209.8818 L 4232.1455 3210.6091 L 4232.1455 3210.9727 L 4232.5091 3211.7 L 4233.6 3212.0636 L 4234.6909 3212.0636 L 4235.7818 3211.7 L 4236.1455 3210.9727 M 4223.0545 3204.4273 L 4223.0545 3210.6091 L 4222.6909 3211.7 L 4221.9636 3212.0636 L 4221.2364 3212.0636 M 4224.1455 3206.9727 L 4221.6 3206.9727 M 4217.0182 3206.9727 L 4217.7455 3207.3364 L 4218.4727 3208.0636 L 4218.8364 3209.1545 L 4218.8364 3209.8818 L 4218.4727 3210.9727 L 4217.7455 3211.7 L 4217.0182 3212.0636 L 4215.9273 3212.0636 L 4215.2 3211.7 L 4214.4727 3210.9727 L 4214.1091 3209.8818 L 4214.1091 3209.1545 L 4214.4727 3208.0636 L 4215.2 3207.3364 L 4215.9273 3206.9727 L 4217.0182 3206.9727 M 4309.4545 3216.4273 L 4309.4545 3224.0636 M 4312 3216.4273 L 4306.9091 3216.4273 M 4300.1455 3218.9727 L 4300.1455 3224.0636 M 4300.1455 3220.0636 L 4300.8727 3219.3364 L 4301.6 3218.9727 L 4302.6909 3218.9727 L 4303.4182 3219.3364 L 4304.1455 3220.0636 L 4304.5091 3221.1545 L 4304.5091 3221.8818 L 4304.1455 3222.9727 L 4303.4182 3223.7 L 4302.6909 3224.0636 L 4301.6 3224.0636 L 4300.8727 3223.7 L 4300.1455 3222.9727 M 4293.7455 3220.0636 L 4294.1091 3219.3364 L 4295.2 3218.9727 L 4296.2909 3218.9727 L 4297.3818 3219.3364 L 4297.7455 3220.0636 L 4297.3818 3220.7909 L 4296.6545 3221.1545 L 4294.8364 3221.5182 L 4294.1091 3221.8818 L 4293.7455 3222.6091 L 4293.7455 3222.9727 L 4294.1091 3223.7 L 4295.2 3224.0636 L 4296.2909 3224.0636 L 4297.3818 3223.7 L 4297.7455 3222.9727 M 4291.3455 3216.4273 L 4291.3455 3224.0636 M 4291.3455 3220.4273 L 4290.2545 3219.3364 L 4289.5273 3218.9727 L 4288.4364 3218.9727 L 4287.7091 3219.3364 L 4287.3455 3220.4273 L 4287.3455 3224.0636 M 4283.8545 3216.4273 L 4283.8545 3222.6091 L 4283.4909 3223.7 L 4282.7636 3224.0636 L 4282.0364 3224.0636 M 4284.9455 3218.9727 L 4282.4 3218.9727 M 4275.2727 3218.9727 L 4275.2727 3224.0636 M 4275.2727 3220.0636 L 4276 3219.3364 L 4276.7273 3218.9727 L 4277.8182 3218.9727 L 4278.5455 3219.3364 L 4279.2727 3220.0636 L 4279.6364 3221.1545 L 4279.6364 3221.8818 L 4279.2727 3222.9727 L 4278.5455 3223.7 L 4277.8182 3224.0636 L 4276.7273 3224.0636 L 4276 3223.7 L 4275.2727 3222.9727 M 4272.8727 3218.9727 L 4272.8727 3224.0636 M 4272.8727 3221.1545 L 4272.5091 3220.0636 L 4271.7818 3219.3364 L 4271.0545 3218.9727 L 4269.9636 3218.9727 M 4267.5636 3216.4273 L 4267.2 3216.7909 L 4266.8364 3216.4273 L 4267.2 3216.0636 L 4267.5636 3216.4273 M 4267.2 3218.9727 L 4267.2 3224.0636 M 4263.7091 3222.6091 L 4264.0727 3222.9727 L 4264.4364 3222.6091 L 4264.0727 3222.2455 L 4263.7091 3222.6091 L 4263.7091 3223.3364 L 4264.4364 3224.0636 M 4251.3455 3216.4273 L 4251.3455 3224.0636 M 4251.3455 3220.0636 L 4252.0727 3219.3364 L 4252.8 3218.9727 L 4253.8909 3218.9727 L 4254.6182 3219.3364 L 4255.3455 3220.0636 L 4255.7091 3221.1545 L 4255.7091 3221.8818 L 4255.3455 3222.9727 L 4254.6182 3223.7 L 4253.8909 3224.0636 L 4252.8 3224.0636 L 4252.0727 3223.7 L 4251.3455 3222.9727 M 4248.9455 3221.1545 L 4244.5818 3221.1545 L 4244.5818 3220.4273 L 4244.9455 3219.7 L 4245.3091 3219.3364 L 4246.0364 3218.9727 L 4247.1273 3218.9727 L 4247.8545 3219.3364 L 4248.5818 3220.0636 L 4248.9455 3221.1545 L 4248.9455 3221.8818 L 4248.5818 3222.9727 L 4247.8545 3223.7 L 4247.1273 3224.0636 L 4246.0364 3224.0636 L 4245.3091 3223.7 L 4244.5818 3222.9727 M 4242.1818 3218.9727 L 4242.1818 3224.0636 M 4242.1818 3220.4273 L 4241.0909 3219.3364 L 4240.3636 3218.9727 L 4239.2727 3218.9727 L 4238.5455 3219.3364 L 4238.1818 3220.4273 L 4238.1818 3224.0636 M 4238.1818 3220.4273 L 4237.0909 3219.3364 L 4236.3636 3218.9727 L 4235.2727 3218.9727 L 4234.5455 3219.3364 L 4234.1818 3220.4273 L 4234.1818 3224.0636 M 4231.7818 3216.4273 L 4231.4182 3216.7909 L 4231.0545 3216.4273 L 4231.4182 3216.0636 L 4231.7818 3216.4273 M 4231.4182 3218.9727 L 4231.4182 3224.0636 M 4228.6545 3216.4273 L 4228.6545 3224.0636 M 4225.0182 3218.9727 L 4228.6545 3222.6091 M 4227.2 3221.1545 L 4224.6545 3224.0636 M 4221.5273 3222.6091 L 4221.8909 3222.9727 L 4222.2545 3222.6091 L 4221.8909 3222.2455 L 4221.5273 3222.6091 L 4221.5273 3223.3364 L 4222.2545 3224.0636 M 4213.5273 3216.4273 L 4213.5273 3224.0636 M 4213.5273 3216.4273 L 4210.2545 3216.4273 L 4209.1636 3216.7909 L 4208.8 3217.1545 L 4208.4364 3217.8818 L 4208.4364 3218.9727 L 4208.8 3219.7 L 4209.1636 3220.0636 L 4210.2545 3220.4273 L 4213.5273 3220.4273 M 4206.0364 3216.4273 L 4206.0364 3224.0636 M 4206.0364 3220.4273 L 4204.9455 3219.3364 L 4204.2182 3218.9727 L 4203.1273 3218.9727 L 4202.4 3219.3364 L 4202.0364 3220.4273 L 4202.0364 3224.0636 M 4199.6364 3216.4273 L 4199.2727 3216.7909 L 4198.9091 3216.4273 L 4199.2727 3216.0636 L 4199.6364 3216.4273 M 4199.2727 3218.9727 L 4199.2727 3224.0636 M 4196.5091 3218.9727 L 4196.5091 3226.6091 M 4196.5091 3220.0636 L 4195.7818 3219.3364 L 4195.0545 3218.9727 L 4193.9636 3218.9727 L 4193.2364 3219.3364 L 4192.5091 3220.0636 L 4192.1455 3221.1545 L 4192.1455 3221.8818 L 4192.5091 3222.9727 L 4193.2364 3223.7 L 4193.9636 3224.0636 L 4195.0545 3224.0636 L 4195.7818 3223.7 L 4196.5091 3222.9727 M 4189.7455 3216.4273 L 4189.7455 3224.0636 M 4187.3455 3216.4273 L 4186.9818 3216.7909 L 4186.6182 3216.4273 L 4186.9818 3216.0636 L 4187.3455 3216.4273 M 4186.9818 3218.9727 L 4186.9818 3224.0636 M 4183.4909 3222.6091 L 4183.8545 3222.9727 L 4184.2182 3222.6091 L 4183.8545 3222.2455 L 4183.4909 3222.6091 L 4183.4909 3223.3364 L 4184.2182 3224.0636 M 4310.9091 3228.4273 L 4310.9091 3234.6091 L 4310.5455 3235.7 L 4309.8182 3236.0636 L 4309.0909 3236.0636 M 4312 3230.9727 L 4309.4545 3230.9727 M 4306.6909 3233.1545 L 4302.3273 3233.1545 L 4302.3273 3232.4273 L 4302.6909 3231.7 L 4303.0545 3231.3364 L 4303.7818 3230.9727 L 4304.8727 3230.9727 L 4305.6 3231.3364 L 4306.3273 3232.0636 L 4306.6909 3233.1545 L 4306.6909 3233.8818 L 4306.3273 3234.9727 L 4305.6 3235.7 L 4304.8727 3236.0636 L 4303.7818 3236.0636 L 4303.0545 3235.7 L 4302.3273 3234.9727 M 4295.5636 3232.0636 L 4296.2909 3231.3364 L 4297.0182 3230.9727 L 4298.1091 3230.9727 L 4298.8364 3231.3364 L 4299.5636 3232.0636 L 4299.9273 3233.1545 L 4299.9273 3233.8818 L 4299.5636 3234.9727 L 4298.8364 3235.7 L 4298.1091 3236.0636 L 4297.0182 3236.0636 L 4296.2909 3235.7 L 4295.5636 3234.9727 M 4293.1636 3228.4273 L 4293.1636 3236.0636 M 4293.1636 3232.4273 L 4292.0727 3231.3364 L 4291.3455 3230.9727 L 4290.2545 3230.9727 L 4289.5273 3231.3364 L 4289.1636 3232.4273 L 4289.1636 3236.0636 M 4283.8545 3228.4273 L 4284.5818 3228.4273 L 4285.3091 3228.7909 L 4285.6727 3229.8818 L 4285.6727 3236.0636 M 4286.7636 3230.9727 L 4284.2182 3230.9727 M 4281.4545 3230.9727 L 4281.4545 3234.6091 L 4281.0909 3235.7 L 4280.3636 3236.0636 L 4279.2727 3236.0636 L 4278.5455 3235.7 L 4277.4545 3234.6091 M 4277.4545 3230.9727 L 4277.4545 3236.0636 M 4275.0545 3230.9727 L 4275.0545 3236.0636 M 4275.0545 3233.1545 L 4274.6909 3232.0636 L 4273.9636 3231.3364 L 4273.2364 3230.9727 L 4272.1455 3230.9727 M 4269.3818 3230.9727 L 4267.2 3236.0636 M 4265.0182 3230.9727 L 4267.2 3236.0636 L 4267.9273 3237.5182 L 4268.6545 3238.2455 L 4269.3818 3238.6091 L 4269.7455 3238.6091 M 4261.8909 3234.6091 L 4262.2545 3234.9727 L 4262.6182 3234.6091 L 4262.2545 3234.2455 L 4261.8909 3234.6091 L 4261.8909 3235.3364 L 4262.6182 3236.0636 M 4253.8909 3228.4273 L 4253.8909 3236.0636 M 4253.8909 3236.0636 L 4249.5273 3236.0636 M 4242.7636 3230.9727 L 4242.7636 3236.0636 M 4242.7636 3232.0636 L 4243.4909 3231.3364 L 4244.2182 3230.9727 L 4245.3091 3230.9727 L 4246.0364 3231.3364 L 4246.7636 3232.0636 L 4247.1273 3233.1545 L 4247.1273 3233.8818 L 4246.7636 3234.9727 L 4246.0364 3235.7 L 4245.3091 3236.0636 L 4244.2182 3236.0636 L 4243.4909 3235.7 L 4242.7636 3234.9727 M 4240.3636 3228.4273 L 4240.3636 3236.0636 M 4240.3636 3228.4273 L 4237.0909 3228.4273 L 4236 3228.7909 L 4235.6364 3229.1545 L 4235.2727 3229.8818 L 4235.2727 3230.9727 L 4235.6364 3231.7 L 4236 3232.0636 L 4237.0909 3232.4273 L 4240.3636 3232.4273 M 4231.0545 3230.9727 L 4231.7818 3231.3364 L 4232.5091 3232.0636 L 4232.8727 3233.1545 L 4232.8727 3233.8818 L 4232.5091 3234.9727 L 4231.7818 3235.7 L 4231.0545 3236.0636 L 4229.9636 3236.0636 L 4229.2364 3235.7 L 4228.5091 3234.9727 L 4228.1455 3233.8818 L 4228.1455 3233.1545 L 4228.5091 3232.0636 L 4229.2364 3231.3364 L 4229.9636 3230.9727 L 4231.0545 3230.9727 M 4225.7455 3230.9727 L 4225.7455 3236.0636 M 4225.7455 3233.1545 L 4225.3818 3232.0636 L 4224.6545 3231.3364 L 4223.9273 3230.9727 L 4222.8364 3230.9727 M 4219.3455 3228.4273 L 4219.3455 3234.6091 L 4218.9818 3235.7 L 4218.2545 3236.0636 L 4217.5273 3236.0636 M 4220.4364 3230.9727 L 4217.8909 3230.9727 M 4210.7636 3230.9727 L 4210.7636 3236.0636 M 4210.7636 3232.0636 L 4211.4909 3231.3364 L 4212.2182 3230.9727 L 4213.3091 3230.9727 L 4214.0364 3231.3364 L 4214.7636 3232.0636 L 4215.1273 3233.1545 L 4215.1273 3233.8818 L 4214.7636 3234.9727 L 4214.0364 3235.7 L 4213.3091 3236.0636 L 4212.2182 3236.0636 L 4211.4909 3235.7 L 4210.7636 3234.9727 M 4195.4909 3231.7 L 4195.4909 3231.3364 L 4195.8545 3230.9727 L 4196.2182 3230.9727 L 4196.5818 3231.3364 L 4196.9455 3232.0636 L 4197.6727 3233.8818 L 4198.4 3234.9727 L 4199.1273 3235.7 L 4199.8545 3236.0636 L 4201.3091 3236.0636 L 4202.0364 3235.7 L 4202.4 3235.3364 L 4202.7636 3234.6091 L 4202.7636 3233.8818 L 4202.4 3233.1545 L 4202.0364 3232.7909 L 4199.4909 3231.3364 L 4199.1273 3230.9727 L 4198.7636 3230.2455 L 4198.7636 3229.5182 L 4199.1273 3228.7909 L 4199.8545 3228.4273 L 4200.5818 3228.7909 L 4200.9455 3229.5182 L 4200.9455 3230.2455 L 4200.5818 3231.3364 L 4199.8545 3232.4273 L 4198.0364 3234.9727 L 4197.3091 3235.7 L 4196.5818 3236.0636 L 4195.8545 3236.0636 L 4195.4909 3235.7 L 4195.4909 3235.3364 M 4183.1273 3230.9727 L 4183.1273 3236.0636 M 4183.1273 3232.0636 L 4183.8545 3231.3364 L 4184.5818 3230.9727 L 4185.6727 3230.9727 L 4186.4 3231.3364 L 4187.1273 3232.0636 L 4187.4909 3233.1545 L 4187.4909 3233.8818 L 4187.1273 3234.9727 L 4186.4 3235.7 L 4185.6727 3236.0636 L 4184.5818 3236.0636 L 4183.8545 3235.7 L 4183.1273 3234.9727 M 4180.7273 3228.4273 L 4180.7273 3236.0636 M 4178.3273 3228.4273 L 4178.3273 3236.0636 M 4169.2364 3228.4273 L 4169.2364 3234.6091 L 4168.8727 3235.7 L 4168.1455 3236.0636 L 4167.4182 3236.0636 M 4170.3273 3230.9727 L 4167.7818 3230.9727 M 4165.0182 3228.4273 L 4165.0182 3236.0636 M 4165.0182 3232.4273 L 4163.9273 3231.3364 L 4163.2 3230.9727 L 4162.1091 3230.9727 L 4161.3818 3231.3364 L 4161.0182 3232.4273 L 4161.0182 3236.0636 M 4158.6182 3233.1545 L 4154.2545 3233.1545 L 4154.2545 3232.4273 L 4154.6182 3231.7 L 4154.9818 3231.3364 L 4155.7091 3230.9727 L 4156.8 3230.9727 L 4157.5273 3231.3364 L 4158.2545 3232.0636 L 4158.6182 3233.1545 L 4158.6182 3233.8818 L 4158.2545 3234.9727 L 4157.5273 3235.7 L 4156.8 3236.0636 L 4155.7091 3236.0636 L 4154.9818 3235.7 L 4154.2545 3234.9727 M 4312 3245.1545 L 4307.6364 3245.1545 L 4307.6364 3244.4273 L 4308 3243.7 L 4308.3636 3243.3364 L 4309.0909 3242.9727 L 4310.1818 3242.9727 L 4310.9091 3243.3364 L 4311.6364 3244.0636 L 4312 3245.1545 L 4312 3245.8818 L 4311.6364 3246.9727 L 4310.9091 3247.7 L 4310.1818 3248.0636 L 4309.0909 3248.0636 L 4308.3636 3247.7 L 4307.6364 3246.9727 M 4305.2364 3242.9727 L 4305.2364 3248.0636 M 4305.2364 3244.4273 L 4304.1455 3243.3364 L 4303.4182 3242.9727 L 4302.3273 3242.9727 L 4301.6 3243.3364 L 4301.2364 3244.4273 L 4301.2364 3248.0636 M 4294.4727 3244.0636 L 4295.2 3243.3364 L 4295.9273 3242.9727 L 4297.0182 3242.9727 L 4297.7455 3243.3364 L 4298.4727 3244.0636 L 4298.8364 3245.1545 L 4298.8364 3245.8818 L 4298.4727 3246.9727 L 4297.7455 3247.7 L 4297.0182 3248.0636 L 4295.9273 3248.0636 L 4295.2 3247.7 L 4294.4727 3246.9727 M 4290.2545 3242.9727 L 4290.9818 3243.3364 L 4291.7091 3244.0636 L 4292.0727 3245.1545 L 4292.0727 3245.8818 L 4291.7091 3246.9727 L 4290.9818 3247.7 L 4290.2545 3248.0636 L 4289.1636 3248.0636 L 4288.4364 3247.7 L 4287.7091 3246.9727 L 4287.3455 3245.8818 L 4287.3455 3245.1545 L 4287.7091 3244.0636 L 4288.4364 3243.3364 L 4289.1636 3242.9727 L 4290.2545 3242.9727 M 4284.9455 3242.9727 L 4284.9455 3246.6091 L 4284.5818 3247.7 L 4283.8545 3248.0636 L 4282.7636 3248.0636 L 4282.0364 3247.7 L 4280.9455 3246.6091 M 4280.9455 3242.9727 L 4280.9455 3248.0636 M 4278.5455 3242.9727 L 4278.5455 3248.0636 M 4278.5455 3245.1545 L 4278.1818 3244.0636 L 4277.4545 3243.3364 L 4276.7273 3242.9727 L 4275.6364 3242.9727 M 4268.8727 3242.9727 L 4268.8727 3248.0636 M 4268.8727 3244.0636 L 4269.6 3243.3364 L 4270.3273 3242.9727 L 4271.4182 3242.9727 L 4272.1455 3243.3364 L 4272.8727 3244.0636 L 4273.2364 3245.1545 L 4273.2364 3245.8818 L 4272.8727 3246.9727 L 4272.1455 3247.7 L 4271.4182 3248.0636 L 4270.3273 3248.0636 L 4269.6 3247.7 L 4268.8727 3246.9727 M 4262.1091 3242.9727 L 4262.1091 3248.7909 L 4262.4727 3249.8818 L 4262.8364 3250.2455 L 4263.5636 3250.6091 L 4264.6545 3250.6091 L 4265.3818 3250.2455 M 4262.1091 3244.0636 L 4262.8364 3243.3364 L 4263.5636 3242.9727 L 4264.6545 3242.9727 L 4265.3818 3243.3364 L 4266.1091 3244.0636 L 4266.4727 3245.1545 L 4266.4727 3245.8818 L 4266.1091 3246.9727 L 4265.3818 3247.7 L 4264.6545 3248.0636 L 4263.5636 3248.0636 L 4262.8364 3247.7 L 4262.1091 3246.9727 M 4259.7091 3240.4273 L 4259.3455 3240.7909 L 4258.9818 3240.4273 L 4259.3455 3240.0636 L 4259.7091 3240.4273 M 4259.3455 3242.9727 L 4259.3455 3248.0636 M 4256.5818 3242.9727 L 4256.5818 3248.0636 M 4256.5818 3244.4273 L 4255.4909 3243.3364 L 4254.7636 3242.9727 L 4253.6727 3242.9727 L 4252.9455 3243.3364 L 4252.5818 3244.4273 L 4252.5818 3248.0636 M 4245.8182 3242.9727 L 4245.8182 3248.7909 L 4246.1818 3249.8818 L 4246.5455 3250.2455 L 4247.2727 3250.6091 L 4248.3636 3250.6091 L 4249.0909 3250.2455 M 4245.8182 3244.0636 L 4246.5455 3243.3364 L 4247.2727 3242.9727 L 4248.3636 3242.9727 L 4249.0909 3243.3364 L 4249.8182 3244.0636 L 4250.1818 3245.1545 L 4250.1818 3245.8818 L 4249.8182 3246.9727 L 4249.0909 3247.7 L 4248.3636 3248.0636 L 4247.2727 3248.0636 L 4246.5455 3247.7 L 4245.8182 3246.9727 M 4234.9091 3240.4273 L 4235.6364 3240.4273 L 4236.3636 3240.7909 L 4236.7273 3241.8818 L 4236.7273 3248.0636 M 4237.8182 3242.9727 L 4235.2727 3242.9727 M 4230.6909 3242.9727 L 4231.4182 3243.3364 L 4232.1455 3244.0636 L 4232.5091 3245.1545 L 4232.5091 3245.8818 L 4232.1455 3246.9727 L 4231.4182 3247.7 L 4230.6909 3248.0636 L 4229.6 3248.0636 L 4228.8727 3247.7 L 4228.1455 3246.9727 L 4227.7818 3245.8818 L 4227.7818 3245.1545 L 4228.1455 3244.0636 L 4228.8727 3243.3364 L 4229.6 3242.9727 L 4230.6909 3242.9727 M 4225.3818 3240.4273 L 4225.3818 3248.0636 M 4222.9818 3240.4273 L 4222.9818 3248.0636 M 4219.3455 3242.9727 L 4222.9818 3246.6091 M 4221.5273 3245.1545 L 4218.9818 3248.0636 M 4206.6182 3242.9727 L 4206.6182 3248.0636 M 4206.6182 3244.0636 L 4207.3455 3243.3364 L 4208.0727 3242.9727 L 4209.1636 3242.9727 L 4209.8909 3243.3364 L 4210.6182 3244.0636 L 4210.9818 3245.1545 L 4210.9818 3245.8818 L 4210.6182 3246.9727 L 4209.8909 3247.7 L 4209.1636 3248.0636 L 4208.0727 3248.0636 L 4207.3455 3247.7 L 4206.6182 3246.9727 M 4203.1273 3240.4273 L 4203.1273 3246.6091 L 4202.7636 3247.7 L 4202.0364 3248.0636 L 4201.3091 3248.0636 M 4204.2182 3242.9727 L 4201.6727 3242.9727 M 4192.2182 3240.4273 L 4192.2182 3246.6091 L 4191.8545 3247.7 L 4191.1273 3248.0636 L 4190.4 3248.0636 M 4193.3091 3242.9727 L 4190.7636 3242.9727 M 4188 3240.4273 L 4188 3248.0636 M 4188 3244.4273 L 4186.9091 3243.3364 L 4186.1818 3242.9727 L 4185.0909 3242.9727 L 4184.3636 3243.3364 L 4184 3244.4273 L 4184 3248.0636 M 4181.6 3245.1545 L 4177.2364 3245.1545 L 4177.2364 3244.4273 L 4177.6 3243.7 L 4177.9636 3243.3364 L 4178.6909 3242.9727 L 4179.7818 3242.9727 L 4180.5091 3243.3364 L 4181.2364 3244.0636 L 4181.6 3245.1545 L 4181.6 3245.8818 L 4181.2364 3246.9727 L 4180.5091 3247.7 L 4179.7818 3248.0636 L 4178.6909 3248.0636 L 4177.9636 3247.7 L 4177.2364 3246.9727 M 4164.8727 3241.5182 L 4165.2364 3240.7909 L 4166.3273 3240.4273 L 4167.0545 3240.4273 L 4168.1455 3240.7909 L 4168.8727 3241.8818 L 4169.2364 3243.7 L 4169.2364 3245.5182 L 4168.8727 3246.9727 L 4168.1455 3247.7 L 4167.0545 3248.0636 L 4166.6909 3248.0636 L 4165.6 3247.7 L 4164.8727 3246.9727 L 4164.5091 3245.8818 L 4164.5091 3245.5182 L 4164.8727 3244.4273 L 4165.6 3243.7 L 4166.6909 3243.3364 L 4167.0545 3243.3364 L 4168.1455 3243.7 L 4168.8727 3244.4273 L 4169.2364 3245.5182 M 4160.2909 3240.4273 L 4161.3818 3240.7909 L 4161.7455 3241.5182 L 4161.7455 3242.2455 L 4161.3818 3242.9727 L 4160.6545 3243.3364 L 4159.2 3243.7 L 4158.1091 3244.0636 L 4157.3818 3244.7909 L 4157.0182 3245.5182 L 4157.0182 3246.6091 L 4157.3818 3247.3364 L 4157.7455 3247.7 L 4158.8364 3248.0636 L 4160.2909 3248.0636 L 4161.3818 3247.7 L 4161.7455 3247.3364 L 4162.1091 3246.6091 L 4162.1091 3245.5182 L 4161.7455 3244.7909 L 4161.0182 3244.0636 L 4159.9273 3243.7 L 4158.4727 3243.3364 L 4157.7455 3242.9727 L 4157.3818 3242.2455 L 4157.3818 3241.5182 L 4157.7455 3240.7909 L 4158.8364 3240.4273 L 4160.2909 3240.4273 M 4154.6182 3240.4273 L 4154.6182 3248.0636 M 4150.9818 3242.9727 L 4154.6182 3246.6091 M 4153.1636 3245.1545 L 4150.6182 3248.0636 M 4148.2182 3240.4273 L 4148.2182 3248.0636 M 4148.2182 3240.4273 L 4145.3091 3248.0636 M 4142.4 3240.4273 L 4145.3091 3248.0636 M 4142.4 3240.4273 L 4142.4 3248.0636 M 4140 3240.4273 L 4140 3248.0636 M 4140 3248.0636 L 4135.6364 3248.0636 M 4130.3273 3240.4273 L 4133.2364 3248.0636 M 4130.3273 3240.4273 L 4127.4182 3248.0636 M 4132.1455 3245.5182 L 4128.5091 3245.5182 M 4124.6545 3246.2455 L 4125.0182 3246.6091 L 4124.6545 3246.9727 L 4124.2909 3246.6091 L 4124.6545 3246.2455~~gge14413~~0~pinpart", 20 | "HOLE~4091.717~3343.37~6.8898~gge12103~0", 21 | "HOLE~4091.717~3118.961~6.8898~gge12106~0", 22 | "HOLE~4312.189~3118.961~6.8898~gge12121~0", 23 | "HOLE~4312.189~3343.37~6.8898~gge12136~0", 24 | "PAD~ELLIPSE~4385.5~3353~6~6~11~~1~1.8~~0~gge14426~0~~Y~0~~~4385.5,3353" 25 | ], 26 | "layers": [ 27 | "1~TopLayer~#FF0000~true~false~true~", 28 | "2~BottomLayer~#0000FF~true~true~true~", 29 | "3~TopSilkLayer~#FFCC00~true~false~true~", 30 | "4~BottomSilkLayer~#66CC33~true~false~true~", 31 | "5~TopPasteMaskLayer~#808080~true~false~true~", 32 | "6~BottomPasteMaskLayer~#800000~true~false~true~", 33 | "7~TopSolderMaskLayer~#800080~true~false~true~0.3", 34 | "8~BottomSolderMaskLayer~#AA00FF~true~false~true~0.3", 35 | "9~Ratlines~#6464FF~false~false~true~", 36 | "10~BoardOutLine~#FF00FF~true~false~true~", 37 | "11~Multi-Layer~#C0C0C0~true~false~true~", 38 | "12~Document~#FFFFFF~true~false~true~", 39 | "13~TopAssembly~#33CC99~false~false~false~", 40 | "14~BottomAssembly~#5555FF~false~false~false~", 41 | "15~Mechanical~#F022F0~false~false~false~", 42 | "19~3DModel~#66CCFF~false~false~false~", 43 | "21~Inner1~#999966~false~false~false~~", 44 | "22~Inner2~#008000~false~false~false~~", 45 | "23~Inner3~#00FF00~false~false~false~~", 46 | "24~Inner4~#BC8E00~false~false~false~~", 47 | "25~Inner5~#70DBFA~false~false~false~~", 48 | "26~Inner6~#00CC66~false~false~false~~", 49 | "27~Inner7~#9966FF~false~false~false~~", 50 | "28~Inner8~#800080~false~false~false~~", 51 | "29~Inner9~#008080~false~false~false~~", 52 | "30~Inner10~#15935F~false~false~false~~", 53 | "31~Inner11~#000080~false~false~false~~", 54 | "32~Inner12~#00B400~false~false~false~~", 55 | "33~Inner13~#2E4756~false~false~false~~", 56 | "34~Inner14~#99842F~false~false~false~~", 57 | "35~Inner15~#FFFFAA~false~false~false~~", 58 | "36~Inner16~#99842F~false~false~false~~", 59 | "37~Inner17~#2E4756~false~false~false~~", 60 | "38~Inner18~#3535FF~false~false~false~~", 61 | "39~Inner19~#8000BC~false~false~false~~", 62 | "40~Inner20~#43AE5F~false~false~false~~", 63 | "41~Inner21~#C3ECCE~false~false~false~~", 64 | "42~Inner22~#728978~false~false~false~~", 65 | "43~Inner23~#39503F~false~false~false~~", 66 | "44~Inner24~#0C715D~false~false~false~~", 67 | "45~Inner25~#5A8A80~false~false~false~~", 68 | "46~Inner26~#2B937E~false~false~false~~", 69 | "47~Inner27~#23999D~false~false~false~~", 70 | "48~Inner28~#45B4E3~false~false~false~~", 71 | "49~Inner29~#215DA1~false~false~false~~", 72 | "50~Inner30~#4564D7~false~false~false~~", 73 | "51~Inner31~#6969E9~false~false~false~~", 74 | "52~Inner32~#9069E9~false~false~false~~", 75 | "99~ComponentShapeLayer~#00CCCC~false~false~false~0.4", 76 | "100~LeadShapeLayer~#CC9999~false~false~false~", 77 | "101~ComponentMarkingLayer~#66FFCC~false~false~false~", 78 | "Hole~Hole~#222222~false~false~true~", 79 | "DRCError~DRCError~#FAD609~false~false~true~" 80 | ], 81 | "objects": [ 82 | "All~true~false", 83 | "Component~true~true", 84 | "Prefix~true~true", 85 | "Name~true~false", 86 | "Track~true~true", 87 | "Pad~true~true", 88 | "Via~true~true", 89 | "Hole~true~true", 90 | "Copper_Area~true~true", 91 | "Circle~true~true", 92 | "Arc~true~true", 93 | "Solid_Region~true~true", 94 | "Text~true~true", 95 | "Image~true~true", 96 | "Rect~true~true", 97 | "Dimension~true~true", 98 | "Protractor~true~true" 99 | ], 100 | "BBox": { 101 | "x": 4076, 102 | "y": 3101.5, 103 | "width": 317.5, 104 | "height": 259.5 105 | }, 106 | "preference": { 107 | "hideFootprints": "", 108 | "hideNets": "" 109 | }, 110 | "DRCRULE": { 111 | "Default": { 112 | "trackWidth": 1, 113 | "clearance": 0.6, 114 | "viaHoleDiameter": 2.4, 115 | "viaHoleD": 1.2 116 | }, 117 | "isRealtime": false, 118 | "isDrcOnRoutingOrPlaceVia": false, 119 | "checkObjectToCopperarea": true, 120 | "showDRCRangeLine": true 121 | }, 122 | "netColors": [] 123 | } --------------------------------------------------------------------------------