├── .gitignore ├── .gitmodules ├── GNUmakefile ├── LICENSE ├── README.md ├── enclosure ├── enclosure-nousb.stl └── enclosure.stl ├── esp8266 ├── GNUmakefile ├── MCP23S18.cpp ├── MCP23S18.h ├── http.cpp ├── mailstation.cpp ├── telnet.cpp ├── update.cpp ├── util.cpp ├── wifistation.h └── wifistation.ino ├── host ├── GNUmakefile └── sendload.c ├── mailstation ├── GNUmakefile ├── flashloader.asm └── wsloader.asm ├── pcb ├── gerber │ ├── copper_bottom.gbr │ ├── copper_top.gbr │ ├── gerber_job.gbrjob │ ├── profile.gbr │ ├── silkscreen_bottom.gbr │ ├── silkscreen_top.gbr │ ├── soldermask_bottom.gbr │ ├── soldermask_top.gbr │ ├── solderpaste_bottom.gbr │ └── solderpaste_top.gbr ├── wifistation.brd └── wifistation.sch └── release ├── flashloader.bin ├── version.txt ├── wifistation.bin └── wsloader.bin /.gitignore: -------------------------------------------------------------------------------- 1 | esp8266/obj 2 | host/sendload 3 | mailstation/*.bin 4 | mailstation/*.ihx 5 | mailstation/*.lk 6 | mailstation/*.lst 7 | mailstation/*.map 8 | mailstation/*.noi 9 | mailstation/*.rel 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "esp8266/lib/Syslog"] 2 | path = esp8266/lib/Syslog 3 | url = https://github.com/arcao/Syslog.git 4 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | SUBDIRS := esp8266 host mailstation 2 | 3 | DOWNLOAD_URL := "https://raw.githubusercontent.com/jcs/WiFiStation/main/release/wifistation.bin" 4 | VERSION := $(shell grep 'define WIFISTATION_VERSION' esp8266/wifistation.h | sed 's/.*"\(.*\)"/\1/') 5 | 6 | all: $(SUBDIRS) 7 | $(SUBDIRS): 8 | $(MAKE) -C $@ 9 | 10 | clean: 11 | for f in $(SUBDIRS); do $(MAKE) -C $$f clean; done 12 | 13 | flash_esp8266: esp8266 14 | env UPLOAD_PORT=/dev/cuaU0 $(MAKE) -C esp8266 flash 15 | 16 | release: all 17 | cp -f esp8266/obj/wifistation_huzzah/wifistation.bin release/ 18 | cp -f mailstation/wsloader.bin release/ 19 | cp -f mailstation/flashloader.bin release/ 20 | echo $(VERSION) > release/version.txt 21 | stat -f "%z" release/wifistation.bin >> release/version.txt 22 | md5 -q release/wifistation.bin >> release/version.txt 23 | echo $(DOWNLOAD_URL) >> release/version.txt 24 | 25 | .PHONY: all clean $(SUBDIRS) 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | All WiFiStation code, schematics, board design, and enclosure are licensed 2 | under an ISC license. 3 | 4 | Copyright (c) 2021 joshua stein 5 | 6 | Permission to use, copy, modify, and distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WiFiStation: A WiFi interface to the MailStation 2 | 3 | This project is a set of firmware, tools, and hardware specifications for a 4 | board housing an ESP8266 module which talks to a 5 | [Cidco MailStation](https://jcs.org/2019/05/03/mailstation) 6 | over its parallel port. 7 | 8 | WiFiStation acts as a modem with an 9 | [`AT` command interface](https://en.wikipedia.org/wiki/Hayes_command_set) 10 | allowing the MailStation to issue `AT` commands to "dial" to telnet addresses 11 | rather than phone numbers. 12 | This allows the MailStation to use software like 13 | [msTERM](https://github.com/jcs/msTERM) 14 | to connect to BBSes over WiFi rather than its internal modem. 15 | 16 | All communication between the WiFiStation and the MailStation is also echoed on 17 | the WiFiStation's USB serial interface, so commands can be sent from a host 18 | computer as well. 19 | 20 | For the latest documentation for this project, please see 21 | [my WiFiStation site](https://jcs.org/wifistation). 22 | 23 | ## Hardware Details 24 | 25 | An 26 | ~~[MCP23017](http://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf)~~ 27 | [MCP23S18](http://ww1.microchip.com/downloads/en/devicedoc/22103a.pdf) 28 | connects to an 29 | [Adafruit Feather HUZZAH ESP8266](https://www.adafruit.com/product/2821) 30 | over ~~I2C~~ SPI and has 12 of its GPIO lines (8 data, 2 input status, and 2 31 | output control) routed to pins on a DB25 connector, which plugs into the 32 | MailStation's printer port. 33 | 34 | The full 35 | [BOM can be found here.](https://octopart.com/bom-tool/1wY3fCEg) 36 | 37 | ## Compiling and Flashing esp8266 Firmware 38 | 39 | On macOS: 40 | 41 | Install Arduino GUI and esp8266 board info per 42 | [these instructions](https://github.com/esp8266/Arduino#installing-with-boards-manager). 43 | 44 | ``` 45 | $ git clone https://github.com/plerup/makeEspArduino 46 | $ git clone https://github.com/jcs/WiFiStation.git 47 | $ cd WiFiStation/esp8266 48 | WiFiStation/esp8266$ make 49 | - Parsing Arduino configuration files ... 50 | - Finding all involved files for the build ... 51 | [...] 52 | WiFiStation/esp8266$ make upload 53 | === Using upload port: /dev/tty.usbserial-02331261 @ 115200 54 | esptool.py v3.0 55 | Serial port /dev/tty.usbserial-02331261 56 | Connecting.... 57 | Chip is ESP8266EX 58 | Features: WiFi 59 | Crystal is 26MHz 60 | [...] 61 | ``` 62 | -------------------------------------------------------------------------------- /enclosure/enclosure-nousb.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcs/WiFiStation/4e81091cba290e6e66405a127a409bf406a7e074/enclosure/enclosure-nousb.stl -------------------------------------------------------------------------------- /enclosure/enclosure.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcs/WiFiStation/4e81091cba290e6e66405a127a409bf406a7e074/enclosure/enclosure.stl -------------------------------------------------------------------------------- /esp8266/GNUmakefile: -------------------------------------------------------------------------------- 1 | # pkg_add makeesparduino 2 | 3 | # targeting the adafruit feather huzzah esp8266 4 | BOARD = huzzah 5 | 6 | # default of -w supresses all warnings 7 | COMP_WARNINGS = -Wall -Wextra 8 | 9 | BUILD_ROOT = $(CURDIR)/obj 10 | EXCLUDE_DIRS = $(BUILD_ROOT) 11 | 12 | UNAME_S := $(shell uname -s) 13 | ifeq ($(UNAME_S),OpenBSD) 14 | ESP_ROOT = /usr/local/share/arduino/hardware/espressif/esp8266 15 | ARDUINO_ROOT = /usr/local/share/arduino 16 | ARDUINO_LIBS = ${ESP_ROOT}/libraries 17 | 18 | UPLOAD_PORT?= /dev/cuaU0 19 | 20 | SERVER_IP?= `route -qn get 0.0.0.0 | grep 'address:' | sed 's/.*: //'` 21 | SERVER_PORT?= 8000 22 | 23 | include /usr/local/share/makeEspArduino/makeEspArduino.mk 24 | else 25 | include ../../makeEspArduino/makeEspArduino.mk 26 | endif 27 | 28 | serve_ota: all 29 | @[ -d release ] || mkdir release 30 | @grep _VERSION wifistation.h | sed -e 's/"$$//' -e 's/.*"//' > release/ota.txt 31 | @cp -f $(MAIN_EXE) release/update.bin 32 | @stat -f "%z" release/update.bin >> release/ota.txt 33 | @md5 -q release/update.bin >> release/ota.txt 34 | @echo http://$(SERVER_IP):$(SERVER_PORT)/update.bin >> release/ota.txt 35 | @echo "" 36 | @echo "Issue update command:" 37 | @echo "" 38 | @echo -n "AT$$" 39 | @echo "UPDATE! http://${SERVER_IP}:${SERVER_PORT}/ota.txt" 40 | @echo "" 41 | cd release && python3 -m http.server $(SERVER_PORT) 42 | -------------------------------------------------------------------------------- /esp8266/MCP23S18.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MCP23S18 16-bit I/O Expander 3 | * Copyright (c) 2021 joshua stein 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #include "MCP23S18.h" 19 | #include 20 | 21 | /* 22 | .---._.---. 23 | VSS | 1 28 | NC 24 | NC | 2 27 | GPA7 25 | GPB0 | 3 26 | GPA6 26 | GPB1 | 4 25 | GPA5 27 | GPB2 | 5 24 | GPA4 28 | GPB3 | 6 23 | GPA3 29 | GPB4 | 7 22 | GPA2 30 | GPB5 | 8 21 | GPA1 31 | GPB6 | 9 20 | GPA0 32 | GPB7 | 10 19 | INTA 33 | VDD | 11 18 | INTB 34 | CS | 12 17 | NC 35 | SCK | 13 16 | RESET 36 | MOSI | 14 15 | MISO 37 | `---------' 38 | */ 39 | 40 | #define WRITE 0 41 | #define READ 1 42 | 43 | void 44 | MCP23S18::begin(uint8_t _cs_pin) 45 | { 46 | cs_pin = _cs_pin; 47 | ::pinMode(cs_pin, OUTPUT); 48 | ::digitalWrite(cs_pin, HIGH); 49 | 50 | SPI.begin(); 51 | 52 | /* 53 | * Disable Sequential Operation Mode and set BANK=1, but use the IOCON 54 | * register at the address it's at when in BANK=0 as it is at power-on 55 | */ 56 | writeRegister(MCP23S18_IOCON_INIT, 57 | MCP23S18_IOCON_SEQOP | MCP23S18_IOCON_BANK); 58 | } 59 | 60 | uint8_t 61 | MCP23S18::readGPIO(uint8_t bank_b) 62 | { 63 | return readRegister(MCP23S18_GPIO + (bank_b ? MCP23S18_BANKB : 0)); 64 | } 65 | 66 | void 67 | MCP23S18::writeGPIO(uint8_t bank_b, uint8_t val) 68 | { 69 | writeRegister(MCP23S18_GPIO + (bank_b ? MCP23S18_BANKB : 0), val); 70 | } 71 | 72 | uint16_t 73 | MCP23S18::readGPIOAB(void) 74 | { 75 | return (readRegister(MCP23S18_BANKB + MCP23S18_GPIO) << 8) | 76 | readRegister(MCP23S18_GPIO); 77 | } 78 | 79 | void 80 | MCP23S18::updateRegisterBit(uint8_t reg, uint8_t bit, bool set) 81 | { 82 | uint8_t val = readRegister(reg); 83 | 84 | if (set) 85 | val |= (1 << bit); 86 | else 87 | val &= ~(1 << bit); 88 | 89 | writeRegister(reg, val); 90 | } 91 | 92 | void 93 | MCP23S18::pinMode(uint8_t pin, uint8_t dir) 94 | { 95 | updateRegisterBit(regForPin(MCP23S18_IODIR, pin), bitForPin(pin), 96 | dir == INPUT ? MCP23S18_IODIR_INPUT : MCP23S18_IODIR_OUTPUT); 97 | } 98 | 99 | void 100 | MCP23S18::bankPinMode(uint8_t bank, uint8_t dir) 101 | { 102 | writeRegister(MCP23S18_IODIR + (bank ? MCP23S18_BANKB : 0), 103 | dir == OUTPUT ? 0 : 0xff); 104 | } 105 | 106 | uint8_t 107 | MCP23S18::digitalRead(uint8_t pin) 108 | { 109 | uint8_t val = readRegister(regForPin(MCP23S18_GPIO, pin)); 110 | return (val & (1 << bitForPin(pin))) ? HIGH : LOW; 111 | } 112 | 113 | void 114 | MCP23S18::digitalWrite(uint8_t pin, uint8_t level) 115 | { 116 | uint8_t val = readRegister(regForPin(MCP23S18_OLAT, pin)); 117 | 118 | if (level == HIGH) 119 | val |= (1 << bitForPin(pin)); 120 | else 121 | val &= ~(1 << bitForPin(pin)); 122 | 123 | writeRegister(regForPin(MCP23S18_OLAT, pin), val); 124 | } 125 | 126 | void 127 | MCP23S18::pullUp(uint8_t pin, uint8_t level) 128 | { 129 | updateRegisterBit(regForPin(MCP23S18_GPPU, pin), bitForPin(pin), 130 | level == HIGH ? 1 : 0); 131 | } 132 | 133 | void 134 | MCP23S18::bankPullUp(uint8_t bank, uint8_t level) 135 | { 136 | writeRegister(MCP23S18_GPPU + (bank ? MCP23S18_BANKB : 0), 137 | level == HIGH ? 0xff : 0); 138 | } 139 | 140 | uint8_t 141 | MCP23S18::readRegister(uint8_t reg) 142 | { 143 | uint8_t val; 144 | 145 | beginSend(READ); 146 | SPI.transfer(reg); 147 | val = SPI.transfer(0x0); 148 | endSend(); 149 | return val; 150 | } 151 | 152 | void 153 | MCP23S18::writeRegister(uint8_t reg, uint8_t b) 154 | { 155 | beginSend(WRITE); 156 | SPI.transfer(reg); 157 | SPI.transfer(b); 158 | endSend(); 159 | } 160 | 161 | void 162 | MCP23S18::beginSend(uint8_t mode) 163 | { 164 | /* 5Mhz */ 165 | SPI.beginTransaction(SPISettings(5000000, MSBFIRST, SPI_MODE0)); 166 | ::digitalWrite(cs_pin, LOW); 167 | 168 | /* 7-bit address is 0x20, shift in the read/write bit */ 169 | SPI.transfer((0x20 << 1) | mode); 170 | } 171 | 172 | void 173 | MCP23S18::endSend(void) 174 | { 175 | ::digitalWrite(cs_pin, HIGH); 176 | SPI.endTransaction(); 177 | } 178 | 179 | inline uint8_t 180 | MCP23S18::regForPin(uint8_t reg, uint8_t pin) 181 | { 182 | return reg + (pin > MCP23S18_BANKA_MAX_PIN ? MCP23S18_BANKB : 0); 183 | } 184 | 185 | inline uint8_t 186 | MCP23S18::bitForPin(uint8_t pin) 187 | { 188 | return pin % 8; 189 | } 190 | -------------------------------------------------------------------------------- /esp8266/MCP23S18.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MCP23S18 16-bit I/O Expander 3 | * Copyright (c) 2021 joshua stein 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #ifndef _MCP23S018_H_ 19 | #define _MCP23S018_H_ 20 | 21 | #include 22 | #include 23 | 24 | class MCP23S18 { 25 | public: 26 | uint8_t cs_pin; 27 | uint16_t pin_states; 28 | 29 | void begin(uint8_t _cs_pin); 30 | 31 | uint8_t readGPIO(uint8_t bank_b); 32 | uint16_t readGPIOAB(void); 33 | void writeGPIO(uint8_t bank_b, uint8_t val); 34 | 35 | uint8_t readRegister(uint8_t reg); 36 | void writeRegister(uint8_t addr, uint8_t b); 37 | void updateRegisterBit(uint8_t reg, uint8_t bit, bool set); 38 | void pinMode(uint8_t pin, uint8_t dir); 39 | void bankPinMode(uint8_t bank, uint8_t dir); 40 | 41 | uint8_t digitalRead(uint8_t b); 42 | void digitalWrite(uint8_t pin, uint8_t level); 43 | void pullUp(uint8_t pin, uint8_t level); 44 | void bankPullUp(uint8_t bank, uint8_t level); 45 | 46 | private: 47 | inline uint8_t regForPin(uint8_t reg, uint8_t pin); 48 | inline uint8_t bitForPin(uint8_t pin); 49 | void beginSend(uint8_t mode); 50 | void endSend(void); 51 | }; 52 | 53 | /* IOCON.BANK = 0 */ 54 | #define MCP23S18_IOCON_INIT 0x0A 55 | 56 | /* IOCON.BANK = 1 */ 57 | #define MCP23S18_IODIR 0x00 /* I/O DIRECTION */ 58 | #define MCP23S18_IODIR_OUTPUT 0x0 59 | #define MCP23S18_IODIR_INPUT 0x1 60 | #define MCP23S18_IPOL 0x01 /* INPUT POLARITY */ 61 | #define MCP23S18_GPINTEN 0x02 /* INTERRUPT-ON-CHANGE CONTROL */ 62 | #define MCP23S18_DEFVAL 0x03 /* DEFAULT COMPARE REGISTER FOR INTERRUPT-ON-CHANGE */ 63 | #define MCP23S18_INTCON 0x04 /* INTERRUPT CONTROL */ 64 | #define MCP23S18_IOCON 0x05 /* CONFIGURATION */ 65 | #define MCP23S18_IOCON_INTCC (1 << 0) /* Interrupt Clearing Control */ 66 | #define MCP23S18_IOCON_INTPOL (1 << 1) /* polarity of the INT output */ 67 | #define MCP23S18_IOCON_ODR (1 << 2) /* INT pin as an open-drain output */ 68 | #define MCP23S18_IOCON_SEQOP (1 << 5) /* Sequential Operation mode */ 69 | #define MCP23S18_IOCON_MIRROR (1 << 6) /* INT pins mirror */ 70 | #define MCP23S18_IOCON_BANK (1 << 7) /* how the registers are addressed */ 71 | #define MCP23S18_GPPU 0x06 /* PULL-UP RESISTOR CONFIGURATION */ 72 | #define MCP23S18_INTF 0x07 /* INTERRUPT FLAG */ 73 | #define MCP23S18_INTCAP 0x08 /* INTERRUPT CAPTURE */ 74 | #define MCP23S18_GPIO 0x09 /* PORT */ 75 | #define MCP23S18_OLAT 0x0A /* OUTPUT LATCH */ 76 | 77 | #define MCP23S18_BANKA 0x00 78 | #define MCP23S18_BANKA_MAX_PIN 7 79 | #define MCP23S18_BANKB 0x10 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /esp8266/http.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * WiFiStation 3 | * Copyright (c) 2021 joshua stein 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #include 19 | #include "wifistation.h" 20 | 21 | std::unique_ptr http = NULL; 22 | 23 | static const char html_wrapper[] PROGMEM = R"END( 24 | 25 | 26 | 27 | WiFiStation at %s 28 | 39 | 40 | 41 |

WiFiStation %s

42 |

43 | %s 44 | 45 | 46 | )END"; 47 | 48 | static unsigned int upload_size = 0; 49 | static unsigned int delivered_bytes = 0; 50 | 51 | void 52 | http_process(void) 53 | { 54 | if (!settings->http_server) 55 | return; 56 | 57 | http->handleClient(); 58 | } 59 | 60 | void 61 | http_send_result(int status, bool include_home, const char *body, ...) 62 | { 63 | va_list arg; 64 | const char *accept = http->header("Accept").c_str(); 65 | int len; 66 | char *doc = NULL; 67 | 68 | /* expand body format */ 69 | va_start(arg, body); 70 | len = vasprintf(&doc, body, arg); 71 | va_end(arg); 72 | 73 | if (len == -1) 74 | goto fail; 75 | 76 | /* if Accept header starts with text/html, client prefers html */ 77 | if (accept && strncmp(accept, "text/html", 9) == 0) { 78 | char *tmp; 79 | 80 | /* append home link to body */ 81 | if (include_home) { 82 | len = asprintf(&tmp, "%s" 83 | "

" 84 | "Home", doc); 85 | if (len == -1) 86 | goto fail; 87 | free(doc); 88 | doc = tmp; 89 | } 90 | 91 | /* insert body into html template */ 92 | len = asprintf(&tmp, html_wrapper, 93 | WiFi.localIP().toString().c_str(), WIFISTATION_VERSION, 94 | doc); 95 | if (len == -1) 96 | goto fail; 97 | free(doc); 98 | doc = tmp; 99 | 100 | http->send(status, "text/html", doc); 101 | } else { 102 | /* append newline since this is probably going to a terminal */ 103 | doc = (char *)realloc(doc, len + 2); 104 | doc[len] = '\n'; 105 | doc[len + 1] = '\0'; 106 | http->send(status, "text/plain", doc); 107 | } 108 | 109 | free(doc); 110 | return; 111 | 112 | fail: 113 | if (doc != NULL) 114 | free(doc); 115 | http->send(500, "text/plain", "out of memory :("); 116 | return; 117 | } 118 | 119 | void 120 | http_setup(void) 121 | { 122 | if (settings->http_server) { 123 | if (http) 124 | return; 125 | 126 | http.reset(new ESP8266WebServer(80)); 127 | } else { 128 | if (http) 129 | http = NULL; 130 | 131 | return; 132 | } 133 | 134 | http->collectHeaders(F("Accept")); 135 | 136 | http->on("/", HTTP_GET, []() { 137 | http_send_result(200, false, R"END( 138 |

139 |

140 | Binary to execute on MailStation (maximum size is %d bytes): 141 |

142 | 143 |
144 |

145 | Ensure the WSLoader application is running on the MailStation ready to accept 146 | the upload. 147 |

148 | 149 |

150 | )END", 151 | MAX_UPLOAD_SIZE, MAX_UPLOAD_SIZE); 152 | }); 153 | 154 | /* 155 | * This measure step is because we don't get the total size of the 156 | * upload until we read the whole thing, but we need to send the file 157 | * size to the MailStation before sending any data. So to avoid 158 | * caching the entire upload, we process the upload and just count the 159 | * bytes being passed through, then send a 307 redirect to the actual 160 | * upload endpoint with the total size. Browsers follow a 307 with a 161 | * POST, so at /upload we'll have the actual size before it sends us 162 | * the same data. 163 | */ 164 | http->on("/upload", HTTP_POST, []() { 165 | char tmp[32]; 166 | 167 | if (upload_size == 0) { 168 | http_send_result(400, true, 169 | "Failed receiving file (size 0)."); 170 | } else { 171 | snprintf(tmp, sizeof(tmp), "/upload_measured?size=%d", 172 | upload_size); 173 | http->sendHeader("Location", tmp); 174 | http->send(307, "text/plain", "Ok, follow me!"); 175 | } 176 | }, []() { 177 | HTTPUpload& upload = http->upload(); 178 | 179 | switch (upload.status) { 180 | case UPLOAD_FILE_START: 181 | upload_size = 0; 182 | break; 183 | case UPLOAD_FILE_WRITE: 184 | upload_size += upload.currentSize; 185 | break; 186 | case UPLOAD_FILE_ABORTED: 187 | upload_size = 0; 188 | /* FALLTHROUGH */ 189 | case UPLOAD_FILE_END: 190 | if (upload_size == 0) { 191 | http_send_result(400, true, 192 | "Failed receiving file (size 0)."); 193 | return; 194 | } 195 | 196 | if (upload_size > MAX_UPLOAD_SIZE) { 197 | http_send_result(400, true, 198 | "File upload cannot be larger than %d " 199 | "bytes.", MAX_UPLOAD_SIZE); 200 | upload_size = 0; 201 | return; 202 | } 203 | 204 | break; 205 | } 206 | }); 207 | 208 | http->on("/upload_measured", HTTP_POST, []() { 209 | http_send_result(200, true, 210 | "Successfully uploaded %d byte%s to MailStation.", 211 | delivered_bytes, delivered_bytes == 1 ? "" : "s"); 212 | }, []() { 213 | HTTPUpload& upload = http->upload(); 214 | 215 | switch (upload.status) { 216 | case UPLOAD_FILE_START: 217 | delivered_bytes = 0; 218 | 219 | if (!(upload_size = atoi(http->arg("size").c_str()))) { 220 | http_send_result(400, true, "No size " 221 | "parameter passed. Perhaps your browser " 222 | "failed to follow the 307 redirect " 223 | "properly."); 224 | return; 225 | } 226 | 227 | if (upload_size > MAX_UPLOAD_SIZE) { 228 | http_send_result(400, true, 229 | "File upload cannot be larger than %d " 230 | "bytes.", MAX_UPLOAD_SIZE); 231 | return; 232 | } 233 | 234 | if (ms_write(upload_size & 0xff) != 0 || 235 | ms_write((upload_size >> 8) & 0xff) != 0) { 236 | http_send_result(400, true, 237 | "Failed sending size bytes " 238 | "(0x%x, 0x%x) to " 239 | "MailStation. Is the WSLoader program " 240 | "running?", 241 | upload_size & 0xff, 242 | (upload_size >> 8) & 0xff); 243 | return; 244 | } 245 | break; 246 | case UPLOAD_FILE_WRITE: 247 | for (int i = 0; i < (int)upload.currentSize; i++) { 248 | delivered_bytes++; 249 | if (ms_write(upload.buf[i]) == -1) { 250 | http_send_result(400, true, 251 | "Failed uploading to MailStation " 252 | "at byte %d/%d.", 253 | delivered_bytes, upload_size); 254 | return; 255 | } 256 | yield(); 257 | delayMicroseconds(500); 258 | } 259 | break; 260 | case UPLOAD_FILE_END: 261 | break; 262 | case UPLOAD_FILE_ABORTED: 263 | http_send_result(400, true, 264 | "Aborted upload to MailStation."); 265 | return; 266 | } 267 | }); 268 | 269 | http->onNotFound([]() { 270 | http_send_result(404, true, "404"); 271 | }); 272 | 273 | http->begin(); 274 | } 275 | -------------------------------------------------------------------------------- /esp8266/mailstation.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * WiFiStation 3 | * Copyright (c) 2021 joshua stein 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #include "MCP23S18.h" 19 | #include "wifistation.h" 20 | 21 | /* millis to consider a read/write timed out */ 22 | #define MAILSTATION_TIMEOUT 2500 23 | 24 | /* 25 | * ESP8266 default SPI pins: 26 | * 27 | * GPIO 12: MISO 28 | * GPIO 13: MOSI 29 | * GPIO 14: SCK 30 | * 31 | * Custom pins will be: 32 | * GPIO 4: CS 33 | * GPIO 16: Reset 34 | */ 35 | 36 | #define GPIO_CS 4 37 | #define GPIO_RESET 16 38 | 39 | /* these are pins on the MCP23S18 */ 40 | const int pData0 = 0; 41 | const int pData1 = 1; 42 | const int pData2 = 2; 43 | const int pData3 = 3; 44 | const int pData4 = 4; 45 | const int pData5 = 5; 46 | const int pData6 = 6; 47 | const int pData7 = 7; 48 | 49 | const int pBusy = 8; /* input from mailstation pin 1 (strobe) */ 50 | const int pAck = 9; /* input from mailstation pin 14 (linefeed) */ 51 | 52 | const int pLineFeed = 10; /* output to mailstation pin 10 (ack) */ 53 | const int pStrobe = 11; /* output to mailstation pin 11 (busy) */ 54 | 55 | MCP23S18 mcp; 56 | 57 | /* cache data pin direction */ 58 | int data_mode = -1; 59 | 60 | void 61 | ms_init(void) 62 | { 63 | /* reset the MCP23S18 */ 64 | pinMode(GPIO_RESET, OUTPUT); 65 | digitalWrite(GPIO_RESET, LOW); 66 | delay(100); 67 | digitalWrite(GPIO_RESET, HIGH); 68 | delay(100); 69 | 70 | mcp.begin(GPIO_CS); 71 | 72 | /* data lines will flip between input/output, start in output mode */ 73 | ms_datadir(OUTPUT); 74 | 75 | /* strobe (control) */ 76 | mcp.pinMode(pStrobe, OUTPUT); 77 | mcp.pullUp(pStrobe, HIGH); 78 | mcp.digitalWrite(pStrobe, LOW); 79 | 80 | /* linefeed (control) */ 81 | mcp.pinMode(pLineFeed, OUTPUT); 82 | mcp.pullUp(pLineFeed, HIGH); 83 | mcp.digitalWrite(pLineFeed, LOW); 84 | 85 | /* ack (status) */ 86 | mcp.pinMode(pAck, INPUT); 87 | mcp.pullUp(pAck, LOW); 88 | 89 | /* busy (status) */ 90 | mcp.pinMode(pBusy, INPUT); 91 | mcp.pullUp(pBusy, LOW); 92 | } 93 | 94 | void 95 | ms_setup(void) 96 | { 97 | uint8_t iocon; 98 | 99 | for (int i = 0; i < 10; i++) { 100 | ms_init(); 101 | iocon = mcp.readRegister(MCP23S18_IOCON); 102 | 103 | if (iocon == 0xff || iocon == 0x0) { 104 | error_flash(); 105 | ms_init(); 106 | } else 107 | break; 108 | } 109 | } 110 | 111 | void 112 | ms_datadir(uint8_t which) 113 | { 114 | if (data_mode == which) 115 | return; 116 | 117 | mcp.bankPinMode(0, which); 118 | mcp.bankPullUp(0, which == OUTPUT ? HIGH : LOW); 119 | data_mode = which; 120 | } 121 | 122 | int 123 | ms_read(void) 124 | { 125 | unsigned long t; 126 | char c; 127 | 128 | /* sender raises strobe (busy) to signal a write */ 129 | if (mcp.digitalRead(pBusy) == LOW) 130 | return -1; 131 | 132 | /* but when both lines are high, something's not right */ 133 | if (mcp.digitalRead(pAck) == HIGH) { 134 | /* 135 | * Both pins will be raised during a reboot, so in that case 136 | * stop talking. 137 | */ 138 | mailstation_alive = false; 139 | return -1; 140 | } 141 | 142 | ms_datadir(INPUT); 143 | 144 | /* raise linefeed (ack) to signal ready to receive */ 145 | mcp.digitalWrite(pLineFeed, HIGH); 146 | 147 | /* sender sees raised ack and writes data */ 148 | 149 | /* sender lowers strobe (busy) once data is ready */ 150 | t = millis(); 151 | while (mcp.digitalRead(pBusy) == HIGH) { 152 | if (millis() - t > MAILSTATION_TIMEOUT) { 153 | mcp.digitalWrite(pLineFeed, LOW); 154 | error_flash(); 155 | return -1; 156 | } 157 | ESP.wdtFeed(); 158 | } 159 | 160 | c = mcp.readGPIO(0); 161 | 162 | /* lower linefeed (ack) when we're done reading */ 163 | mcp.digitalWrite(pLineFeed, LOW); 164 | 165 | return c; 166 | } 167 | 168 | uint16_t 169 | ms_status(void) 170 | { 171 | return mcp.readGPIOAB(); 172 | } 173 | 174 | int 175 | ms_write(char c) 176 | { 177 | unsigned long t; 178 | 179 | ms_datadir(OUTPUT); 180 | 181 | /* raise strobe (busy on receiver) to signal write */ 182 | mcp.digitalWrite(pStrobe, HIGH); 183 | 184 | /* wait for receiver to raise ack (linefeed on receiver) */ 185 | t = millis(); 186 | while (mcp.digitalRead(pAck) == LOW && 187 | mcp.digitalRead(pLineFeed) == LOW) { 188 | if (millis() - t > MAILSTATION_TIMEOUT) { 189 | mcp.digitalWrite(pStrobe, LOW); 190 | error_flash(); 191 | mailstation_alive = false; 192 | return -1; 193 | } 194 | ESP.wdtFeed(); 195 | } 196 | 197 | /* ack is high, write data */ 198 | ms_writedata(c); 199 | 200 | /* lower strobe (busy on receiver) to indicate we're done writing */ 201 | mcp.digitalWrite(pStrobe, LOW); 202 | 203 | /* wait for receiver to read and lower ack (busy on their end) */ 204 | t = millis(); 205 | while (mcp.digitalRead(pAck) == HIGH) { 206 | if (millis() - t > MAILSTATION_TIMEOUT) { 207 | error_flash(); 208 | return -1; 209 | } 210 | ESP.wdtFeed(); 211 | } 212 | 213 | return 0; 214 | } 215 | 216 | int 217 | ms_print(String str) 218 | { 219 | size_t len = str.length(); 220 | int ret; 221 | 222 | for (size_t i = 0; i < len; i++) 223 | if ((ret = ms_write(str.charAt(i))) != 0) 224 | return ret; 225 | 226 | return 0; 227 | } 228 | 229 | int 230 | ms_print(char *string) 231 | { 232 | size_t len = strlen(string); 233 | int ret; 234 | 235 | for (size_t i = 0; i < len; i++) 236 | if ((ret = ms_write(string[i])) != 0) 237 | return ret; 238 | 239 | return 0; 240 | } 241 | 242 | void 243 | ms_writedata(char c) 244 | { 245 | mcp.writeGPIO(0, c); 246 | } 247 | -------------------------------------------------------------------------------- /esp8266/telnet.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * WiFiStation 3 | * Copyright (c) 2021 joshua stein 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #include "wifistation.h" 19 | 20 | WiFiClient telnet; 21 | 22 | enum { 23 | TELNET_STATE_DISCONNECTED = 0, 24 | TELNET_STATE_CONNECTED, 25 | TELNET_STATE_IAC, 26 | TELNET_STATE_IAC_WILL, 27 | TELNET_STATE_IAC_WONT, 28 | TELNET_STATE_IAC_DO, 29 | TELNET_STATE_IAC_DONT, 30 | TELNET_STATE_IAC_SB, 31 | }; 32 | 33 | uint8_t telnet_state = TELNET_STATE_DISCONNECTED; 34 | uint8_t telnet_sb[64]; 35 | uint8_t telnet_sb_len = 0; 36 | 37 | #define SE 240 /* end of sub-negotiation options */ 38 | #define SB 250 /* start of sub-negotiation options */ 39 | #define WILL 251 /* confirm willingness to negotiate */ 40 | #define WONT 252 /* confirm unwillingness to negotiate */ 41 | #define DO 253 /* indicate willingness to negotiate */ 42 | #define DONT 254 /* indicate unwillingness to negotiate */ 43 | #define IAC 255 /* start of a negotiation sequence */ 44 | 45 | #define IS 0 /* sub-negotiation */ 46 | #define SEND 1 /* sub-negotiation */ 47 | 48 | #define IAC_BINARY 0 /* Transmit Binary */ 49 | #define IAC_ECHO 1 /* Echo Option */ 50 | #define IAC_SGA 3 /* Suppress Go Ahead Option */ 51 | #define IAC_STATUS 5 /* Status Option */ 52 | #define IAC_TM 6 /* Timing Mark Option */ 53 | #define IAC_NAOCRD 10 /* Output Carriage-Return Disposition Option */ 54 | #define IAC_NAOHTS 11 /* Output Horizontal Tabstops Option */ 55 | #define IAC_NAOHTD 12 /* Output Horizontal Tab Disposition Option */ 56 | #define IAC_NAOFFD 13 /* Output Formfeed Disposition Option */ 57 | #define IAC_NAOVTS 14 /* Output Vertical Tabstops Option */ 58 | #define IAC_NAOVTD 15 /* Output Vertical Tab Disposition Option */ 59 | #define IAC_NAOLFD 16 /* Output Linefeed Disposition */ 60 | #define IAC_XASCII 17 /* Extended Ascii Option */ 61 | #define IAC_LOGOUT 18 /* Logout Option */ 62 | #define IAC_BM 19 /* Byte Macro Option */ 63 | #define IAC_SUPDUP 22 /* SUPDUP-OUTPUT Option */ 64 | #define IAC_SENDLOC 23 /* SEND-LOCATION Option */ 65 | #define IAC_TTYPE 24 /* Terminal Type Option */ 66 | #define IAC_EOR 25 /* End of Record Option */ 67 | #define IAC_OUTMRK 27 /* Marking Telnet Option */ 68 | #define IAC_TTYLOC 28 /* Terminal Location Number Option */ 69 | #define IAC_DET 20 /* Data Entry Terminal Option DODIIS */ 70 | #define IAC_X3PAD 30 /* X.3 PAD Option */ 71 | #define IAC_NAWS 31 /* Window Size Option */ 72 | #define IAC_TSPEED 32 /* Terminal Speed Option */ 73 | #define IAC_FLOWCTRL 33 /* Remote Flow Control Option */ 74 | #define IAC_LINEMODE 34 /* Linemode Option */ 75 | #define IAC_XDISPLOC 35 /* X Display Location Option */ 76 | #define IAC_ENVIRON 36 /* Environment Option */ 77 | #define IAC_AUTH 37 /* Authentication */ 78 | #define IAC_ENCRYPT 38 /* Encryption Option */ 79 | #define IAC_NEWENV 39 /* Environment Option */ 80 | #define IAC_CHARSET 42 /* Charset Option */ 81 | #define IAC_COMPORT 44 /* Com Port Control Option */ 82 | 83 | #ifdef TELNET_IAC_TRACE 84 | #define TELNET_IAC_DEBUG(...) { syslog.logf(LOG_INFO, __VA_ARGS__); delay(1); } 85 | #else 86 | #define TELNET_IAC_DEBUG(...) {} 87 | #endif 88 | 89 | #ifdef TELNET_DATA_TRACE 90 | #define TELNET_DATA_DEBUG(...) { syslog.logf(LOG_INFO, __VA_ARGS__); delay(1); } 91 | #else 92 | #define TELNET_DATA_DEBUG(...) {} 93 | #endif 94 | 95 | int 96 | telnet_connect(char *host, uint16_t port) 97 | { 98 | if (telnet_state != TELNET_STATE_DISCONNECTED) 99 | telnet_disconnect(); 100 | 101 | if (!telnet.connect(host, port)) 102 | return 1; 103 | 104 | telnet.setNoDelay(true); 105 | 106 | telnet_state = TELNET_STATE_CONNECTED; 107 | 108 | if (settings->telnet) { 109 | /* start by sending things we support */ 110 | TELNET_IAC_DEBUG("%s: -> IAC DO SUPPRESS GO AHEAD", __func__); 111 | telnet.printf("%c%c%c", IAC, DO, IAC_SGA); 112 | TELNET_IAC_DEBUG("%s: -> IAC WILL TTYPE", __func__); 113 | telnet.printf("%c%c%c", IAC, WILL, IAC_TTYPE); 114 | TELNET_IAC_DEBUG("%s: -> IAC WILL NAWS", __func__); 115 | telnet.printf("%c%c%c", IAC, WILL, IAC_NAWS); 116 | TELNET_IAC_DEBUG("%s: -> IAC WILL TSPEED", __func__); 117 | telnet.printf("%c%c%c", IAC, WILL, IAC_TSPEED); 118 | TELNET_IAC_DEBUG("%s: -> IAC WONT LINEMODE", __func__); 119 | telnet.printf("%c%c%c", IAC, WONT, IAC_LINEMODE); 120 | TELNET_IAC_DEBUG("%s: -> IAC DO STATUS", __func__); 121 | telnet.printf("%c%c%c", IAC, DO, IAC_STATUS); 122 | } 123 | 124 | return 0; 125 | } 126 | 127 | bool 128 | telnet_connected(void) 129 | { 130 | if (telnet_state == TELNET_STATE_DISCONNECTED) 131 | return false; 132 | 133 | if (!telnet.connected()) { 134 | if (telnet_state != TELNET_STATE_DISCONNECTED) 135 | telnet_disconnect(); 136 | return false; 137 | } 138 | 139 | return true; 140 | } 141 | 142 | void 143 | telnet_disconnect(void) 144 | { 145 | telnet.stop(); 146 | telnet_state = TELNET_STATE_DISCONNECTED; 147 | } 148 | 149 | #ifdef TELNET_IAC_TRACE 150 | static char iac_name[16]; 151 | char * 152 | telnet_iac_name(char iac) 153 | { 154 | if (telnet_state == TELNET_STATE_CONNECTED && iac != IAC) { 155 | sprintf(iac_name, "%d (%c)", iac, iac); 156 | return iac_name; 157 | } 158 | 159 | switch (iac) { 160 | case IAC_ECHO: 161 | sprintf(iac_name, "ECHO"); 162 | break; 163 | case IAC_SGA: 164 | sprintf(iac_name, "SGA"); 165 | break; 166 | case IAC_STATUS: 167 | sprintf(iac_name, "STATUS"); 168 | break; 169 | case IAC_TTYPE: 170 | sprintf(iac_name, "TTYPE"); 171 | break; 172 | case IAC_NAWS: 173 | sprintf(iac_name, "NAWS"); 174 | break; 175 | case IAC_TSPEED: 176 | sprintf(iac_name, "TSPEED"); 177 | break; 178 | case IAC_FLOWCTRL: 179 | sprintf(iac_name, "FLOWCTRL"); 180 | break; 181 | case IAC_LINEMODE: 182 | sprintf(iac_name, "LINEMODE"); 183 | break; 184 | case IAC_ENCRYPT: 185 | sprintf(iac_name, "ENCRYPT"); 186 | break; 187 | case IAC_AUTH: 188 | sprintf(iac_name, "AUTH"); 189 | break; 190 | case IAC_XDISPLOC: 191 | sprintf(iac_name, "XDISPLOC"); 192 | break; 193 | case IAC_NEWENV: 194 | sprintf(iac_name, "NEWENV"); 195 | break; 196 | case IAC_ENVIRON: 197 | sprintf(iac_name, "OLD ENVIRON"); 198 | break; 199 | case SE: 200 | sprintf(iac_name, "SE"); 201 | break; 202 | case SB: 203 | sprintf(iac_name, "SB"); 204 | break; 205 | case WILL: 206 | sprintf(iac_name, "WILL"); 207 | break; 208 | case WONT: 209 | sprintf(iac_name, "WONT"); 210 | break; 211 | case DO: 212 | sprintf(iac_name, "DO"); 213 | break; 214 | case DONT: 215 | sprintf(iac_name, "DONT"); 216 | break; 217 | case IAC: 218 | sprintf(iac_name, "IAC"); 219 | break; 220 | default: 221 | sprintf(iac_name, "%d", iac); 222 | break; 223 | } 224 | return iac_name; 225 | } 226 | #endif 227 | 228 | void 229 | telnet_send_ttype(void) 230 | { 231 | TELNET_IAC_DEBUG("%s: -> IAC SB TTYPE IS %s IAC SE", __func__, 232 | settings->telnet_tterm); 233 | telnet.printf("%c%c%c%c", IAC, SB, IAC_TTYPE, IS); 234 | for (size_t i = 0; i < sizeof(settings->telnet_tterm); i++) { 235 | if (settings->telnet_tterm[i] == '\0') 236 | break; 237 | 238 | if (settings->telnet_tterm[i] == IAC) 239 | telnet.write(IAC); 240 | telnet.write(settings->telnet_tterm[i]); 241 | } 242 | telnet.printf("%c%c", IAC, SE); 243 | } 244 | 245 | void 246 | telnet_send_naws(void) 247 | { 248 | TELNET_IAC_DEBUG("%s: -> IAC SB NAWS IS %dx%d IAC SE", __func__, 249 | settings->telnet_tts_w, settings->telnet_tts_h); 250 | 251 | telnet.printf("%c%c%c", IAC, SB, IAC_NAWS); 252 | 253 | /* we only support 8-bit settings, but NAWS is 16-bit * */ 254 | telnet.write(0); 255 | if (settings->telnet_tts_w == IAC) 256 | telnet.write(IAC); 257 | telnet.write(settings->telnet_tts_w); 258 | 259 | telnet.write(0); 260 | if (settings->telnet_tts_h == IAC) 261 | telnet.write(IAC); 262 | telnet.write(settings->telnet_tts_h); 263 | 264 | telnet.printf("%c%c", IAC, SE); 265 | } 266 | 267 | void 268 | telnet_send_tspeed(void) 269 | { 270 | TELNET_IAC_DEBUG("%s: -> IAC SB TSPEED IS %d,%d IAC SE", __func__, 271 | Serial.baudRate(), Serial.baudRate()); 272 | 273 | telnet.printf("%c%c%c%c%d,%d", IAC, SB, IAC_TSPEED, IS, 274 | Serial.baudRate(), Serial.baudRate()); 275 | 276 | telnet.printf("%c%c", IAC, SE); 277 | } 278 | 279 | int 280 | telnet_read(void) 281 | { 282 | char b; 283 | 284 | if (!telnet.available()) 285 | return -1; 286 | 287 | /* when AT$NET=0, just pass everything as-is */ 288 | if (!settings->telnet) 289 | return telnet.read(); 290 | 291 | b = telnet.peek(); 292 | if (telnet_state != TELNET_STATE_CONNECTED || b == IAC) 293 | TELNET_IAC_DEBUG("telnet_read[%s]: %s", 294 | (telnet_state == TELNET_STATE_CONNECTED ? "connected" : 295 | (telnet_state == TELNET_STATE_IAC ? "IAC" : 296 | (telnet_state == TELNET_STATE_IAC_WILL ? "WILL" : 297 | (telnet_state == TELNET_STATE_IAC_WONT ? "WONT" : 298 | (telnet_state == TELNET_STATE_IAC_DO ? "DO" : 299 | (telnet_state == TELNET_STATE_IAC_DONT ? "DONT" : 300 | (telnet_state == TELNET_STATE_IAC_SB ? "SB" : "?"))))))), 301 | telnet_iac_name(b)); 302 | 303 | switch (telnet_state) { 304 | case TELNET_STATE_CONNECTED: 305 | b = telnet.read(); 306 | switch (b) { 307 | case IAC: 308 | telnet_state = TELNET_STATE_IAC; 309 | break; 310 | default: 311 | return b; 312 | } 313 | break; 314 | case TELNET_STATE_IAC: 315 | /* don't consume byte yet */ 316 | switch (b) { 317 | case IAC: 318 | /* escaped IAC, return one IAC */ 319 | telnet_state = TELNET_STATE_CONNECTED; 320 | return telnet.read(); 321 | case WILL: 322 | /* server can do something */ 323 | telnet_state = TELNET_STATE_IAC_WILL; 324 | break; 325 | case WONT: 326 | /* server will not do something */ 327 | telnet_state = TELNET_STATE_IAC_WONT; 328 | break; 329 | case DO: 330 | /* server wants us to do something */ 331 | telnet_state = TELNET_STATE_IAC_DO; 332 | break; 333 | case DONT: 334 | /* server wants us to not do something */ 335 | telnet_state = TELNET_STATE_IAC_DONT; 336 | break; 337 | case SB: 338 | /* sub-negotiate */ 339 | telnet_state = TELNET_STATE_IAC_SB; 340 | telnet_sb_len = 0; 341 | break; 342 | default: 343 | /* something else, return the original IAC */ 344 | telnet_state = TELNET_STATE_CONNECTED; 345 | return IAC; 346 | /* this next non-IAC byte will get returned next */ 347 | } 348 | /* consume byte */ 349 | telnet.read(); 350 | break; 351 | case TELNET_STATE_IAC_SB: 352 | /* keep reading until we see [^IAC] IAC SE */ 353 | b = telnet.read(); 354 | TELNET_IAC_DEBUG("telnet_read: SB[%d] %s", 355 | telnet_sb_len, telnet_iac_name(b)); 356 | if (b == SE && telnet_sb_len > 0 && 357 | telnet_sb[telnet_sb_len - 1] == IAC) { 358 | TELNET_IAC_DEBUG("telnet_read: processing SB"); 359 | if (telnet_sb[1] == SEND) { 360 | switch (telnet_sb[0]) { 361 | case IAC_TTYPE: 362 | telnet_send_ttype(); 363 | break; 364 | case IAC_NAWS: 365 | telnet_send_naws(); 366 | break; 367 | case IAC_TSPEED: 368 | telnet_send_tspeed(); 369 | break; 370 | default: 371 | TELNET_IAC_DEBUG("unsupported IAC SB %s", 372 | telnet_iac_name(telnet_sb[0])); 373 | } 374 | } else { 375 | syslog.logf(LOG_WARNING, "%s: server is " 376 | "telling us SB %d?", __func__, telnet_sb[0]); 377 | } 378 | telnet_state = TELNET_STATE_CONNECTED; 379 | } else { 380 | if (telnet_sb_len < sizeof(telnet_sb)) 381 | telnet_sb[telnet_sb_len++] = b; 382 | else { 383 | syslog.logf(LOG_ERR, "IAC SB overflow!"); 384 | telnet_state = TELNET_STATE_CONNECTED; 385 | telnet_sb_len = 0; 386 | } 387 | } 388 | break; 389 | case TELNET_STATE_IAC_WILL: 390 | switch (b = telnet.read()) { 391 | case IAC_ECHO: 392 | TELNET_IAC_DEBUG("telnet_read: -> IAC DO ECHO"); 393 | telnet.printf("%c%c%c", IAC, DO, b); 394 | break; 395 | case IAC_SGA: 396 | TELNET_IAC_DEBUG("telnet_read: -> IAC DO SGA"); 397 | telnet.printf("%c%c%c", IAC, DO, b); 398 | break; 399 | case IAC_ENCRYPT: 400 | /* refuse with DONT to satisfy NetBSD's telnetd */ 401 | TELNET_IAC_DEBUG("telnet_read: -> IAC DONT ENCRYPT"); 402 | telnet.printf("%c%c%c", IAC, DONT, b); 403 | break; 404 | } 405 | telnet_state = TELNET_STATE_CONNECTED; 406 | break; 407 | case TELNET_STATE_IAC_WONT: 408 | switch (b = telnet.read()) { 409 | default: 410 | /* we don't care about any of these yet */ 411 | break; 412 | } 413 | telnet_state = TELNET_STATE_CONNECTED; 414 | break; 415 | case TELNET_STATE_IAC_DO: 416 | b = telnet.read(); 417 | switch (b) { 418 | case IAC_BINARY: 419 | TELNET_IAC_DEBUG("telnet_read: -> IAC WILL BINARY"); 420 | telnet.printf("%c%c%c", IAC, WILL, b); 421 | break; 422 | case IAC_NAWS: 423 | telnet_send_naws(); 424 | break; 425 | case IAC_TSPEED: 426 | case IAC_TTYPE: 427 | case IAC_FLOWCTRL: 428 | break; 429 | case IAC_LINEMODE: 430 | /* refuse this, we want the server to handle input */ 431 | /* FALLTHROUGH */ 432 | default: 433 | TELNET_IAC_DEBUG("telnet_read: -> IAC WONT %s", 434 | telnet_iac_name(b)); 435 | telnet.printf("%c%c%c", IAC, WONT, b); 436 | break; 437 | } 438 | telnet_state = TELNET_STATE_CONNECTED; 439 | break; 440 | case TELNET_STATE_IAC_DONT: 441 | switch (b = telnet.read()) { 442 | default: 443 | TELNET_IAC_DEBUG("telnet_read: IAC DONT %s", 444 | telnet_iac_name(b)); 445 | break; 446 | } 447 | telnet_state = TELNET_STATE_CONNECTED; 448 | break; 449 | default: 450 | b = telnet.read(); 451 | TELNET_IAC_DEBUG("telnet_read: read 0x%x but in state %d", b, 452 | telnet_state); 453 | break; 454 | } 455 | 456 | return -1; 457 | } 458 | 459 | int 460 | telnet_write(char b) 461 | { 462 | /* escape */ 463 | if (settings->telnet && b == IAC) { 464 | TELNET_DATA_DEBUG("telnet_write: escaped IAC"); 465 | telnet.write(b); 466 | } 467 | 468 | TELNET_DATA_DEBUG("telnet_write: 0x%x", b); 469 | return telnet.write(b); 470 | } 471 | 472 | int 473 | telnet_write(String s) 474 | { 475 | String s2 = ""; 476 | 477 | for (size_t i = 0; i < s.length(); i++) { 478 | /* escape */ 479 | if (settings->telnet && s.charAt(i) == IAC) { 480 | TELNET_DATA_DEBUG("telnet_write: escaped IAC"); 481 | s2 += IAC; 482 | } 483 | s2 += s.charAt(i); 484 | TELNET_DATA_DEBUG("telnet_write: 0x%x", s.charAt(i)); 485 | } 486 | 487 | return telnet.print(s2); 488 | } 489 | -------------------------------------------------------------------------------- /esp8266/update.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * WiFiStation 3 | * Copyright (c) 2021 joshua stein 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #include 19 | #include 20 | #include "wifistation.h" 21 | 22 | static const char OTA_VERSION_URL[] PROGMEM = 23 | "https://raw.githubusercontent.com/jcs/WiFiStation/main/release/version.txt"; 24 | 25 | static WiFiClient client; 26 | static WiFiClientSecure client_tls; 27 | static bool tls = false; 28 | 29 | bool 30 | update_http_read_until_body(const char *url, long expected_length) 31 | { 32 | char *host, *path, *colon; 33 | int status = 0, port, httpver, chars, lines, tlength, clength = -1; 34 | 35 | if (WiFi.status() != WL_CONNECTED) { 36 | output("ERROR WiFi is not connected\r\n"); 37 | return false; 38 | } 39 | 40 | if (url == NULL) { 41 | outputf("ERROR failed parsing NULL URL\r\n"); 42 | return false; 43 | } 44 | 45 | host = (char *)malloc(strlen(url) + 1); 46 | if (host == NULL) { 47 | output("ERROR malloc failed\r\n"); 48 | return false; 49 | } 50 | 51 | path = (char *)malloc(strlen(url) + 1); 52 | if (path == NULL) { 53 | output("ERROR malloc failed\r\n"); 54 | free(host); 55 | return false; 56 | } 57 | 58 | if (sscanf(url, "http://%[^/]%s%n", host, path, &chars) == 2 && 59 | chars != 0) { 60 | tls = false; 61 | } else if (sscanf(url, "https://%[^/]%s%n", host, path, &chars) == 2 62 | && chars != 0) { 63 | tls = true; 64 | /* 65 | * This would be nice to not have to do, but keeping up with 66 | * GitHub's TLS cert fingerprints will be tedious, and we have 67 | * no cert chain. 68 | */ 69 | client_tls.setInsecure(); 70 | } else { 71 | outputf("ERROR failed parsing URL \"%s\"\r\n", url); 72 | free(path); 73 | free(host); 74 | return false; 75 | } 76 | 77 | if ((colon = strchr(host, ':'))) { 78 | colon[0] = '\0'; 79 | port = atoi(colon + 1); 80 | } else { 81 | port = (tls ? 443 : 80); 82 | } 83 | 84 | #ifdef UPDATE_TRACE 85 | syslog.logf(LOG_DEBUG, "%s: host \"%s\" path \"%s\" port %d tls %d", 86 | __func__, host, path, port, tls ? 1 : 0); 87 | #endif 88 | 89 | if (!(tls ? client_tls : client).connect(host, port)) { 90 | outputf("ERROR OTA failed connecting to http%s://%s:%d\r\n", 91 | tls ? "s" : "", host, port); 92 | free(path); 93 | free(host); 94 | return false; 95 | } 96 | 97 | (tls ? client_tls : client).printf("GET %s HTTP/1.0\r\n", path); 98 | (tls ? client_tls : client).printf("Host: %s\r\n", host); 99 | (tls ? client_tls : client).printf("User-Agent: WiFiStation %s\r\n", 100 | WIFISTATION_VERSION); 101 | (tls ? client_tls : client).printf("Connection: close\r\n"); 102 | (tls ? client_tls : client).printf("\r\n"); 103 | 104 | free(path); 105 | free(host); 106 | 107 | /* read headers */ 108 | lines = 0; 109 | while ((tls ? client_tls : client).connected() || 110 | (tls ? client_tls : client).available()) { 111 | String line = (tls ? client_tls : client).readStringUntil('\n'); 112 | 113 | #ifdef UPDATE_TRACE 114 | syslog.logf(LOG_DEBUG, "%s: read header \"%s\"", __func__, 115 | line.c_str()); 116 | #endif 117 | 118 | if (lines == 0) 119 | sscanf(line.c_str(), "HTTP/1.%d %d%n", &httpver, 120 | &status, &chars); 121 | else if (sscanf(line.c_str(), "Content-Length: %d%n", 122 | &tlength, &chars) == 1 && chars > 0) 123 | clength = tlength; 124 | else if (line == "\r") 125 | break; 126 | 127 | lines++; 128 | } 129 | 130 | #ifdef UPDATE_TRACE 131 | syslog.logf(LOG_DEBUG, "%s: read status %d, content-length %d vs " 132 | "expected %ld", __func__, status, clength, expected_length); 133 | #endif 134 | 135 | if (status != 200) { 136 | outputf("ERROR OTA fetch of %s failed with HTTP status %d\r\n", 137 | url, status); 138 | goto drain; 139 | } 140 | 141 | if (expected_length != 0 && clength != expected_length) { 142 | outputf("ERROR OTA fetch of %s expected to be size %d, " 143 | "fetched %d\r\n", url, expected_length, clength); 144 | goto drain; 145 | } 146 | 147 | while ((tls ? client_tls : client).connected() && 148 | !(tls ? client_tls : client).available()) 149 | ESP.wdtFeed(); 150 | 151 | return true; 152 | 153 | drain: 154 | #ifdef UPDATE_TRACE 155 | syslog.logf(LOG_DEBUG, "%s: draining remaining body", __func__); 156 | #endif 157 | while ((tls ? client_tls : client).available()) 158 | (tls ? client_tls : client).read(); 159 | (tls ? client_tls : client).stop(); 160 | return false; 161 | } 162 | 163 | void 164 | update_process(char *url, bool do_update, bool force) 165 | { 166 | String rom_url, md5, version; 167 | int bytesize = 0, lines = 0, len; 168 | char *furl = NULL; 169 | 170 | output("\n"); 171 | 172 | if (url == NULL) { 173 | furl = url = (char *)malloc(len = (strlen_P(OTA_VERSION_URL) + 174 | 1)); 175 | if (url == NULL) { 176 | output("ERROR malloc failed\r\n"); 177 | return; 178 | } 179 | memcpy_P(url, OTA_VERSION_URL, len); 180 | } 181 | 182 | #ifdef UPDATE_TRACE 183 | syslog.logf(LOG_DEBUG, "fetching update manifest from \"%s\"", url); 184 | #endif 185 | 186 | if (!update_http_read_until_body(url, 0)) { 187 | #ifdef UPDATE_TRACE 188 | syslog.logf(LOG_DEBUG, "read until body(%s) failed", url); 189 | #endif 190 | if (furl) 191 | free(furl); 192 | return; 193 | } 194 | if (furl) 195 | free(furl); 196 | 197 | lines = 0; 198 | while ((tls ? client_tls : client).connected() || 199 | (tls ? client_tls : client).available()) { 200 | String line = (tls ? client_tls : client).readStringUntil('\n'); 201 | 202 | #ifdef UPDATE_TRACE 203 | syslog.logf(LOG_DEBUG, "%s: read body[%d] \"%s\"", __func__, 204 | lines, line.c_str()); 205 | #endif 206 | 207 | switch (lines) { 208 | case 0: 209 | version = line; 210 | break; 211 | case 1: 212 | bytesize = atoi(line.c_str()); 213 | break; 214 | case 2: 215 | md5 = line; 216 | break; 217 | case 3: 218 | rom_url = line; 219 | break; 220 | default: 221 | #ifdef UPDATE_TRACE 222 | syslog.logf("%s: unexpected line %d: %s\r\n", __func__, 223 | lines + 1, line.c_str()); 224 | #endif 225 | break; 226 | } 227 | 228 | lines++; 229 | } 230 | 231 | (tls ? client_tls : client).stop(); 232 | 233 | if (version == WIFISTATION_VERSION && !force) { 234 | outputf("ERROR OTA server reports version %s, no update " 235 | "available\r\n", version.c_str()); 236 | return; 237 | } else if (!do_update) { 238 | outputf("OK version %s (%d bytes) available, use AT$UPDATE " 239 | "to update\r\n", version.c_str(), bytesize); 240 | return; 241 | } 242 | 243 | /* doing an update, parse the url read */ 244 | #ifdef UPDATE_TRACE 245 | syslog.logf(LOG_DEBUG, "%s: doing update with ROM url \"%s\" size %d", 246 | __func__, rom_url.c_str(), bytesize); 247 | #endif 248 | if (!update_http_read_until_body((char *)rom_url.c_str(), bytesize)) 249 | return; 250 | 251 | outputf("Updating to version %s (%d bytes) from %s\r\n", 252 | version.c_str(), bytesize, (char *)rom_url.c_str()); 253 | 254 | Update.begin(bytesize, U_FLASH, pRedLED); 255 | 256 | Update.setMD5(md5.c_str()); 257 | 258 | Update.onProgress([](unsigned int progress, unsigned int total) { 259 | outputf("\rFlash update progress: % 6u of % 6u", progress, 260 | total); 261 | }); 262 | 263 | if ((int)Update.writeStream((tls ? client_tls : client)) != bytesize) { 264 | switch (Update.getError()) { 265 | case UPDATE_ERROR_BOOTSTRAP: 266 | outputf("\nERROR update must be done from fresh " 267 | "reset, not from uploaded code\r\n"); 268 | break; 269 | case UPDATE_ERROR_MAGIC_BYTE: 270 | outputf("\nERROR image does not start with 0xE9\r\n"); 271 | break; 272 | default: 273 | outputf("\nERROR failed writing download bytes: %d\r\n", 274 | Update.getError()); 275 | } 276 | 277 | while ((tls ? client_tls : client).available()) 278 | (tls ? client_tls : client).read(); 279 | 280 | (tls ? client_tls : client).stop(); 281 | return; 282 | } 283 | 284 | if (!Update.end()) { 285 | outputf("\nERROR failed update at finish: %d\r\n", 286 | Update.getError()); 287 | return; 288 | } 289 | 290 | (tls ? client_tls : client).stop(); 291 | outputf("\r\nOK update completed, restarting\r\n"); 292 | 293 | delay(500); 294 | ESP.restart(); 295 | } 296 | -------------------------------------------------------------------------------- /esp8266/util.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * WiFiStation 3 | * Copyright (c) 2021 joshua stein 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #include "wifistation.h" 19 | 20 | struct eeprom_data *settings; 21 | 22 | bool serial_alive = true; 23 | bool mailstation_alive = false; 24 | 25 | WiFiUDP syslogUDPClient; 26 | Syslog syslog(syslogUDPClient, SYSLOG_PROTO_BSD); 27 | 28 | #define BOOKMARK_0 "klud.ge" 29 | 30 | void 31 | setup(void) 32 | { 33 | static_assert(sizeof(struct eeprom_data) < EEPROM_SIZE, 34 | "EEPROM_SIZE is not large enough to hold struct eeprom_data"); 35 | 36 | EEPROM.begin(EEPROM_SIZE); 37 | settings = (struct eeprom_data *)EEPROM.getDataPtr(); 38 | if (memcmp(settings->magic, EEPROM_MAGIC_BYTES, 39 | sizeof(settings->magic)) == 0) { 40 | /* do migrations if needed based on current revision */ 41 | switch (settings->revision) { 42 | case 1: 43 | settings->http_server = 0; 44 | /* FALLTHROUGH */ 45 | case 2: 46 | memset(settings->bookmarks, 0, 47 | BOOKMARK_SIZE * NUM_BOOKMARKS); 48 | strcpy(settings->bookmarks[0], BOOKMARK_0); 49 | /* FALLTHROUGH */ 50 | case 3: 51 | settings->echo = 1; 52 | settings->quiet = 0; 53 | settings->verbal = 1; 54 | } 55 | 56 | if (settings->revision != EEPROM_REVISION) { 57 | settings->revision = EEPROM_REVISION; 58 | EEPROM.commit(); 59 | } 60 | } else { 61 | /* start over */ 62 | memset(settings, 0, sizeof(struct eeprom_data)); 63 | memcpy(settings->magic, EEPROM_MAGIC_BYTES, 64 | sizeof(settings->magic)); 65 | settings->revision = EEPROM_REVISION; 66 | 67 | settings->baud = 9600; 68 | 69 | settings->telnet = 1; 70 | strlcpy(settings->telnet_tterm, "ansi", 71 | sizeof(settings->telnet_tterm)); 72 | /* msTERM defaults */ 73 | settings->telnet_tts_w = 64; 74 | settings->telnet_tts_h = 15; 75 | 76 | settings->http_server = 0; 77 | 78 | settings->echo = 1; 79 | settings->quiet = 0; 80 | settings->verbal = 1; 81 | 82 | memset(settings->bookmarks, 0, 83 | BOOKMARK_SIZE * NUM_BOOKMARKS); 84 | strcpy(settings->bookmarks[0], BOOKMARK_0); 85 | 86 | EEPROM.commit(); 87 | } 88 | 89 | syslog_setup(); 90 | 91 | Serial.begin(settings->baud); 92 | delay(1000); 93 | 94 | led_setup(); 95 | ms_setup(); 96 | led_reset(); 97 | 98 | WiFi.persistent(false); 99 | WiFi.mode(WIFI_STA); 100 | 101 | /* don't require wifi_pass in case it's an open network */ 102 | if (settings->wifi_ssid[0] == 0) 103 | WiFi.disconnect(); 104 | else 105 | WiFi.begin(settings->wifi_ssid, settings->wifi_pass); 106 | 107 | http_setup(); 108 | } 109 | 110 | void 111 | syslog_setup(void) 112 | { 113 | if (settings->syslog_server[0]) 114 | syslog.server(settings->syslog_server, 514); 115 | else 116 | syslog.server(NULL, 514); 117 | } 118 | 119 | void 120 | led_setup(void) 121 | { 122 | /* setup LEDs */ 123 | pinMode(pRedLED, OUTPUT); 124 | pinMode(pBlueLED, OUTPUT); 125 | led_reset(); 126 | } 127 | 128 | void 129 | error_flash(void) 130 | { 131 | digitalWrite(pRedLED, LOW); 132 | digitalWrite(pBlueLED, HIGH); 133 | delay(100); 134 | digitalWrite(pRedLED, HIGH); 135 | digitalWrite(pBlueLED, LOW); 136 | delay(100); 137 | digitalWrite(pBlueLED, HIGH); 138 | } 139 | 140 | void 141 | led_reset(void) 142 | { 143 | digitalWrite(pRedLED, HIGH); 144 | digitalWrite(pBlueLED, HIGH); 145 | } 146 | 147 | size_t 148 | outputf(const char *format, ...) 149 | { 150 | va_list arg; 151 | char temp[64]; 152 | char* buf; 153 | 154 | va_start(arg, format); 155 | size_t len = vsnprintf(temp, sizeof(temp), format, arg); 156 | va_end(arg); 157 | 158 | if (len > sizeof(temp) - 1) { 159 | /* too big for stack buffer, malloc something bigger */ 160 | buf = (char *)malloc(len + 1); 161 | if (!buf) 162 | return 0; 163 | 164 | va_start(arg, format); 165 | vsnprintf(buf, len + 1, format, arg); 166 | va_end(arg); 167 | } else 168 | buf = temp; 169 | 170 | output(buf); 171 | 172 | if (buf != temp) 173 | free(buf); 174 | 175 | return len; 176 | } 177 | 178 | int 179 | output(char c) 180 | { 181 | if (serial_alive) { 182 | Serial.write(c); 183 | if (c == '\n') 184 | Serial.flush(); 185 | } 186 | if (mailstation_alive) 187 | ms_write(c); 188 | 189 | return 0; 190 | } 191 | 192 | int 193 | output(const char *str) 194 | { 195 | size_t len = strlen(str); 196 | 197 | #ifdef OUTPUT_TRACE 198 | syslog.logf(LOG_DEBUG, "output: \"%s\"", str); 199 | #endif 200 | 201 | for (size_t i = 0; i < len; i++) 202 | output(str[i]); 203 | 204 | return 0; 205 | } 206 | 207 | int 208 | output(String str) 209 | { 210 | size_t len = str.length(); 211 | char *buf = (char *)malloc(len + 1); 212 | int ret; 213 | 214 | if (buf == NULL) 215 | return -1; 216 | 217 | str.toCharArray(buf, len); 218 | ret = output(buf); 219 | free(buf); 220 | 221 | return ret; 222 | } 223 | -------------------------------------------------------------------------------- /esp8266/wifistation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * WiFiStation 3 | * Copyright (c) 2021 joshua stein 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #ifndef __WIFISTATION_H__ 19 | #define __WIFISTATION_H__ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #define WIFISTATION_VERSION "0.9.2" 28 | 29 | /* enable various debugging */ 30 | // #define AT_TRACE 31 | // #define OUTPUT_TRACE 32 | // #define TELNET_IAC_TRACE 33 | // #define UPDATE_TRACE 34 | 35 | #define EEPROM_SIZE 512 36 | struct __attribute((__packed__)) eeprom_data { 37 | char magic[3]; 38 | #define EEPROM_MAGIC_BYTES "jcs" 39 | uint8_t revision; 40 | #define EEPROM_REVISION 4 41 | char wifi_ssid[64]; 42 | char wifi_pass[64]; 43 | uint32_t baud; 44 | char telnet_tterm[32]; 45 | uint8_t telnet_tts_w; 46 | uint8_t telnet_tts_h; 47 | uint8_t telnet; 48 | uint8_t http_server; 49 | #define BOOKMARK_SIZE 64 50 | #define NUM_BOOKMARKS 3 51 | char bookmarks[NUM_BOOKMARKS][BOOKMARK_SIZE]; 52 | char syslog_server[64]; 53 | uint8_t echo; 54 | uint8_t quiet; 55 | uint8_t verbal; 56 | }; 57 | 58 | extern struct eeprom_data *settings; 59 | extern Syslog syslog; 60 | 61 | #define MAX_UPLOAD_SIZE 65535 62 | 63 | /* these are ESP8266 pins */ 64 | const int pRedLED = 0; 65 | const int pBlueLED = 2; 66 | 67 | /* wifistation.ino */ 68 | void exec_cmd(char *cmd, size_t len); 69 | extern bool serial_alive; 70 | extern bool mailstation_alive; 71 | 72 | /* util.cpp */ 73 | void syslog_setup(void); 74 | void led_setup(void); 75 | void led_reset(void); 76 | void error_flash(void); 77 | size_t outputf(const char *format, ...); 78 | int output(char c); 79 | int output(const char *str); 80 | int output(String str); 81 | 82 | /* mailstation.cpp */ 83 | extern const int pData0; 84 | extern const int pData1; 85 | extern const int pData2; 86 | extern const int pData3; 87 | extern const int pData4; 88 | extern const int pData5; 89 | extern const int pData6; 90 | extern const int pData7; 91 | extern const int pBusy; 92 | extern const int pAck; 93 | extern const int pLineFeed; 94 | extern const int pStrobe; 95 | void ms_setup(void); 96 | void ms_datadir(uint8_t which); 97 | int ms_read(void); 98 | uint16_t ms_status(void); 99 | int ms_write(char c); 100 | int ms_print(char *string); 101 | int ms_print(String); 102 | void ms_writedata(char c); 103 | 104 | /* telnet.cpp */ 105 | int telnet_connect(char *host, uint16_t port); 106 | bool telnet_connected(void); 107 | void telnet_disconnect(void); 108 | int telnet_read(void); 109 | int telnet_write(char b); 110 | int telnet_write(String s); 111 | 112 | /* http.cpp */ 113 | void http_setup(void); 114 | void http_process(void); 115 | 116 | /* update.cpp */ 117 | void update_process(char *, bool, bool); 118 | 119 | #endif 120 | -------------------------------------------------------------------------------- /esp8266/wifistation.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * WiFiStation 3 | * Copyright (c) 2021 joshua stein 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | /* 19 | * Useful AT command sets to emulate: 20 | * 21 | * USRobotics Courier 56K Business Modem 22 | * http://web.archive.org/web/20161116174421/http://support.usr.com/support/3453c/3453c-ug/alphabetic.html 23 | * 24 | * Xecom XE3314L 25 | * http://web.archive.org/web/20210816224031/http://static6.arrow.com/aropdfconversion/63e466a4e0c7e004c40f79e4dbe4c1356a3dcef6/xe3314l.pdf 26 | */ 27 | 28 | #include "wifistation.h" 29 | #include "MCP23S18.h" 30 | 31 | enum { 32 | STATE_AT, 33 | STATE_TELNET, 34 | }; 35 | 36 | static char curcmd[128] = { 0 }; 37 | static char lastcmd[128] = { 0 }; 38 | static char lasthost[128] = { 0 }; 39 | static uint16_t lastport = 0; 40 | static unsigned int curcmdlen = 0; 41 | static unsigned int lastcmdlen = 0; 42 | static uint8_t state = STATE_AT; 43 | static int plusses = 0; 44 | static unsigned long plus_wait = 0; 45 | static bool wifi_ever_connected = false; 46 | 47 | void 48 | loop(void) 49 | { 50 | int b = -1, i; 51 | long now = millis(); 52 | 53 | if (!wifi_ever_connected && WiFi.status() == WL_CONNECTED) { 54 | rst_info *resetInfo = ESP.getResetInfoPtr(); 55 | syslog.logf(LOG_INFO, "connected, reset from %s (%d)", 56 | ESP.getResetReason().c_str(), 57 | (resetInfo == nullptr ? 0 : (*resetInfo).reason)); 58 | wifi_ever_connected = true; 59 | } 60 | 61 | http_process(); 62 | 63 | switch (state) { 64 | case STATE_AT: 65 | if ((b = ms_read()) != -1) { 66 | if (!mailstation_alive) { 67 | /* mailstation can only come alive sending 'a' */ 68 | if (b != 'A' && b != 'a') 69 | return; 70 | mailstation_alive = true; 71 | } 72 | } else if (Serial.available() && (b = Serial.read())) 73 | serial_alive = true; 74 | else 75 | return; 76 | 77 | /* USR modem mode, ignore input not starting with at or a/ */ 78 | if (curcmdlen == 0 && (b != 'A' && b != 'a')) { 79 | return; 80 | } else if (curcmdlen == 1 && b == '/') { 81 | if (settings->echo) 82 | output("/\r"); 83 | curcmd[0] = '\0'; 84 | curcmdlen = 0; 85 | exec_cmd((char *)&lastcmd, lastcmdlen); 86 | break; 87 | } else if (curcmdlen == 1 && (b != 'T' && b != 't')) { 88 | if (settings->echo) 89 | output("\b \b"); 90 | curcmdlen = 0; 91 | return; 92 | } 93 | 94 | switch (b) { 95 | case '\n': 96 | case '\r': 97 | if (b == '\r') { 98 | /* if sender is using \r\n, ignore the \n */ 99 | now = millis(); 100 | while (millis() - now < 10) { 101 | int b2 = Serial.peek(); 102 | if (b2 == -1) 103 | continue; 104 | else if (b2 == '\n') { 105 | /* this is a \r\n, ignore \n */ 106 | Serial.read(); 107 | break; 108 | } else { 109 | /* some other data */ 110 | break; 111 | } 112 | } 113 | } 114 | output("\r"); 115 | curcmd[curcmdlen] = '\0'; 116 | exec_cmd((char *)&curcmd, curcmdlen); 117 | curcmd[0] = '\0'; 118 | curcmdlen = 0; 119 | break; 120 | case '\b': 121 | case 127: 122 | if (curcmdlen) { 123 | if (settings->echo) 124 | output("\b \b"); 125 | curcmdlen--; 126 | } 127 | break; 128 | default: 129 | curcmd[curcmdlen++] = b; 130 | output(b); 131 | } 132 | break; 133 | case STATE_TELNET: 134 | b = -1; 135 | 136 | if (mailstation_alive && (b = ms_read()) != -1) { 137 | if (b == '\e') { 138 | /* probably a multi-character command */ 139 | String seq = String((char)b); 140 | unsigned long t = millis(); 141 | 142 | while (millis() - t < 50) { 143 | if ((b = ms_read()) != -1) 144 | seq += (char)b; 145 | } 146 | telnet_write(seq); 147 | plusses = 0; 148 | break; 149 | } 150 | } else if (Serial.available() && (b = Serial.read())) 151 | serial_alive = true; 152 | 153 | if (b == -1 && plus_wait > 0 && (millis() - plus_wait) >= 500) { 154 | /* received no input within 500ms of a plus */ 155 | if (plusses >= 3) { 156 | state = STATE_AT; 157 | if (!settings->quiet) { 158 | if (settings->verbal) 159 | output("\r\nOK\r\n"); 160 | else 161 | output("0\r"); 162 | } 163 | } else { 164 | /* cancel, flush any plus signs received */ 165 | for (i = 0; i < plusses; i++) 166 | telnet_write("+"); 167 | } 168 | plusses = 0; 169 | plus_wait = 0; 170 | } else if (b != -1) { 171 | if (b == '+') { 172 | plusses++; 173 | plus_wait = millis(); 174 | break; 175 | } 176 | 177 | if (plusses) { 178 | for (i = 0; i < plusses; i++) 179 | telnet_write("+"); 180 | plusses = 0; 181 | } 182 | plus_wait = 0; 183 | telnet_write(b); 184 | break; 185 | } 186 | 187 | if ((b = telnet_read()) != -1) { 188 | if (mailstation_alive) 189 | ms_write(b); 190 | if (serial_alive) 191 | Serial.write(b); 192 | return; 193 | } else if (!telnet_connected()) { 194 | if (!settings->quiet) { 195 | if (settings->verbal) 196 | output("\r\nNO CARRIER\r\n"); 197 | else 198 | output("3\r"); 199 | } 200 | state = STATE_AT; 201 | break; 202 | } 203 | break; 204 | } 205 | } 206 | 207 | void 208 | exec_cmd(char *cmd, size_t len) 209 | { 210 | unsigned long t; 211 | char *errstr = NULL; 212 | char *lcmd, *olcmd; 213 | char cmd_char; 214 | uint8_t cmd_num = 0; 215 | bool did_nl = false; 216 | bool did_response = false; 217 | 218 | lcmd = olcmd = (char *)malloc(len + 1); 219 | if (lcmd == NULL) { 220 | if (settings->verbal) 221 | outputf("ERROR malloc %zu failed\r\n", len); 222 | else 223 | output("4\r"); 224 | return; 225 | } 226 | 227 | #ifdef AT_TRACE 228 | syslog.logf(LOG_DEBUG, "%s: parsing \"%s\"", __func__, cmd); 229 | #endif 230 | 231 | for (size_t i = 0; i < len; i++) 232 | lcmd[i] = tolower(cmd[i]); 233 | lcmd[len] = '\0'; 234 | 235 | /* shouldn't be able to get here, but just in case */ 236 | if (len < 2 || lcmd[0] != 'a' || lcmd[1] != 't') { 237 | errstr = strdup("not an AT command"); 238 | goto error; 239 | } 240 | 241 | memcpy(&lastcmd, lcmd, len + 1); 242 | lastcmdlen = len; 243 | 244 | /* strip AT */ 245 | cmd += 2; 246 | lcmd += 2; 247 | len -= 2; 248 | 249 | parse_cmd: 250 | if (lcmd[0] == '\0') 251 | goto done_parsing; 252 | 253 | /* remove command character */ 254 | cmd_char = lcmd[0]; 255 | len--; 256 | cmd++; 257 | lcmd++; 258 | 259 | /* find optional single digit after command, defaulting to 0 */ 260 | cmd_num = 0; 261 | if (cmd[0] >= '0' && cmd[0] <= '9') { 262 | if (cmd[1] >= '0' && cmd[1] <= '9') 263 | /* nothing uses more than 1 digit */ 264 | goto error; 265 | cmd_num = cmd[0] - '0'; 266 | len--; 267 | cmd++; 268 | lcmd++; 269 | } 270 | 271 | #ifdef AT_TRACE 272 | syslog.logf(LOG_DEBUG, "%s: parsing AT %c[%d] args \"%s\"", __func__, 273 | cmd_char, cmd_num, lcmd); 274 | #endif 275 | 276 | switch (cmd_char) { 277 | case 'd': { 278 | char *host, *ohost, *bookmark; 279 | uint16_t port; 280 | int chars; 281 | int index; 282 | 283 | switch (lcmd[0]) { 284 | case 't': 285 | if (len < 2) 286 | goto error; 287 | 288 | /* ATDT: dial a host */ 289 | host = ohost = (char *)malloc(len); 290 | if (host == NULL) 291 | goto error; 292 | host[0] = '\0'; 293 | if (sscanf(lcmd, "t%[^:]:%u%n", host, &port, 294 | &chars) == 2 && chars > 0) 295 | /* matched host:port */ 296 | ; 297 | else if (sscanf(lcmd, "t%s %u%n", host, &port, 298 | &chars) == 2 && chars > 0) 299 | /* matched host port */ 300 | ; 301 | else if (sscanf(lcmd, "t%[^:]%n", host, &chars) == 1 302 | && chars > 0) 303 | /* host without port */ 304 | port = 23; 305 | else { 306 | errstr = strdup("invalid hostname"); 307 | goto error; 308 | } 309 | break; 310 | case 'l': 311 | if (lcmd[1] == '?') { 312 | /* ATDL?: show last dialed host */ 313 | if (lasthost[0] == '\0') 314 | output("\n\r\n"); 315 | else 316 | outputf("\n%s:%d\r\n", lasthost, 317 | lastport); 318 | did_nl = true; 319 | len = 0; 320 | goto done_parsing; 321 | } else if (lcmd[1] == '\0') { 322 | /* ATDL: re-dial last dialed host */ 323 | if (lasthost[0] == '\0') 324 | goto error; 325 | int len = strlen(lasthost) + 1; 326 | host = ohost = (char *)malloc(len); 327 | if (host == NULL) 328 | goto error; 329 | strlcpy(host, lasthost, len); 330 | port = lastport; 331 | } else 332 | goto error; 333 | break; 334 | case 's': 335 | /* ATDS: dial a stored host */ 336 | if (sscanf(lcmd, "s%d", &index) != 1) 337 | goto error; 338 | 339 | if (index < 1 || index > NUM_BOOKMARKS) { 340 | errstr = strdup("invalid index"); 341 | goto error; 342 | } 343 | 344 | bookmark = settings->bookmarks[index - 1]; 345 | 346 | host = ohost = (char *)malloc(BOOKMARK_SIZE); 347 | if (host == NULL) 348 | goto error; 349 | 350 | host[0] = '\0'; 351 | 352 | if (sscanf(bookmark, "%[^:]:%hu%n", host, &port, 353 | &chars) == 2 && chars > 0) 354 | /* matched host:port */ 355 | ; 356 | else if (sscanf(bookmark, "%[^:]%n", host, &chars) == 1 357 | && chars > 0) 358 | /* host without port */ 359 | port = 23; 360 | else { 361 | errstr = strdup("invalid hostname"); 362 | goto error; 363 | } 364 | break; 365 | default: 366 | goto error; 367 | } 368 | 369 | /* no commands can follow */ 370 | len = 0; 371 | 372 | /* skip leading spaces */ 373 | while (host[0] == ' ') 374 | host++; 375 | 376 | if (host[0] == '\0') { 377 | errstr = strdup("blank hostname"); 378 | goto error; 379 | } 380 | 381 | strlcpy(lasthost, host, sizeof(lasthost)); 382 | lastport = port; 383 | 384 | if (!settings->quiet && settings->verbal) 385 | outputf("\nDIALING %s:%d\r\n", host, port); 386 | 387 | if (telnet_connect(host, port) == 0) { 388 | if (!settings->quiet) { 389 | if (settings->verbal) 390 | outputf("CONNECT %d %s:%d\r\n", 391 | settings->baud, host, port); 392 | else 393 | output("18\r"); /* 57600 */ 394 | } 395 | state = STATE_TELNET; 396 | } else if (!settings->quiet) { 397 | if (settings->verbal) 398 | output("\nNO ANSWER\r\n"); 399 | else 400 | output("8\r"); 401 | } 402 | 403 | did_nl = true; 404 | free(ohost); 405 | break; 406 | } 407 | case 'e': 408 | /* ATE/ATE0 or ATE1: disable or enable echo */ 409 | switch (cmd_num) { 410 | case 0: 411 | settings->echo = 0; 412 | break; 413 | case 1: 414 | settings->echo = 1; 415 | break; 416 | default: 417 | goto error; 418 | } 419 | break; 420 | case 'h': 421 | /* ATH/ATH0: hangup */ 422 | switch (cmd_num) { 423 | case 0: 424 | telnet_disconnect(); 425 | break; 426 | default: 427 | goto error; 428 | } 429 | break; 430 | case 'i': 431 | /* ATI/ATI#: show information pages */ 432 | switch (cmd_num) { 433 | case 0: { 434 | /* ATI0: show settings */ 435 | ip4_addr_t t_addr; 436 | output("\n"); 437 | 438 | outputf("Firmware version: %s\r\n", 439 | WIFISTATION_VERSION); 440 | outputf("Default baud rate: %d\r\n", settings->baud); 441 | outputf("Current baud rate: %d\r\n", 442 | Serial.baudRate()); 443 | outputf("Default WiFi SSID: %s\r\n", 444 | settings->wifi_ssid); 445 | outputf("Current WiFi SSID: %s\r\n", WiFi.SSID()); 446 | outputf("WiFi connected: %s\r\n", 447 | WiFi.status() == WL_CONNECTED ? "yes" : "no"); 448 | if (WiFi.status() == WL_CONNECTED) { 449 | outputf("IP address: %s\r\n", 450 | WiFi.localIP().toString().c_str()); 451 | outputf("Gateway IP: %s\r\n", 452 | WiFi.gatewayIP().toString().c_str()); 453 | outputf("DNS server IP: %s\r\n", 454 | WiFi.dnsIP().toString().c_str()); 455 | } 456 | outputf("MailStation alive: %s\r\n", 457 | mailstation_alive ? "yes" : "no"); 458 | outputf("HTTP server: %s\r\n", 459 | settings->http_server ? "yes" : "no"); 460 | outputf("Syslog server: %s\r\n", 461 | settings->syslog_server); 462 | for (int i = 0; i < NUM_BOOKMARKS; i++) { 463 | if (settings->bookmarks[i][0] != '\0') 464 | outputf("ATDS bookmark %d: %s\r\n", 465 | i + 1, settings->bookmarks[i]); 466 | } 467 | did_nl = true; 468 | break; 469 | } 470 | default: 471 | goto error; 472 | } 473 | break; 474 | case 'o': 475 | /* ATO: go back online after a +++ */ 476 | switch (cmd_num) { 477 | case 0: 478 | if (telnet_connected()) 479 | state = STATE_TELNET; 480 | else 481 | goto error; 482 | break; 483 | default: 484 | goto error; 485 | } 486 | break; 487 | case 'q': 488 | /* ATQ/ATQ0 or ATQ1: enable or disable quiet */ 489 | switch (cmd_num) { 490 | case 0: 491 | settings->quiet = 0; 492 | break; 493 | case 1: 494 | case 2: 495 | settings->quiet = 1; 496 | break; 497 | default: 498 | goto error; 499 | } 500 | break; 501 | case 'v': 502 | /* ATV/ATV0 or ATV1: enable or disable verbal responses */ 503 | switch (cmd_num) { 504 | case 0: 505 | settings->verbal = 0; 506 | break; 507 | case 1: 508 | settings->verbal = 1; 509 | break; 510 | default: 511 | goto error; 512 | } 513 | break; 514 | case 'x': 515 | /* ATX/ATX#: ignore dialtone, certain results (not used) */ 516 | break; 517 | case 'z': 518 | /* ATZ/ATZ0: restart */ 519 | switch (cmd_num) { 520 | case 0: 521 | if (!settings->quiet) { 522 | if (settings->verbal) 523 | output("\nOK\r\n"); 524 | else 525 | output("0\r"); 526 | } 527 | ESP.restart(); 528 | /* NOTREACHED */ 529 | default: 530 | goto error; 531 | } 532 | break; 533 | case '$': 534 | /* wifi232 commands, all consume the rest of the input string */ 535 | if (strcmp(lcmd, "http=0") == 0) { 536 | /* AT$HTTP=0: disable http server */ 537 | settings->http_server = 0; 538 | http_setup(); 539 | } else if (strcmp(lcmd, "http=1") == 0) { 540 | /* AT$HTTP=1: enable http server */ 541 | settings->http_server = 1; 542 | http_setup(); 543 | } else if (strcmp(lcmd, "http?") == 0) { 544 | /* AT$HTTP?: show http server setting */ 545 | outputf("\n%d\r\n", settings->http_server); 546 | } else if (strcmp(lcmd, "net=0") == 0) { 547 | /* AT$NET=0: disable telnet setting */ 548 | settings->telnet = 0; 549 | } else if (strcmp(lcmd, "net=1") == 0) { 550 | /* AT$NET=1: enable telnet setting */ 551 | settings->telnet = 1; 552 | } else if (strcmp(lcmd, "net?") == 0) { 553 | /* AT$NET?: show telnet setting */ 554 | outputf("\n%d\r\n", settings->telnet); 555 | } else if (strncmp(lcmd, "pass=", 5) == 0) { 556 | /* AT$PASS=...: store wep/wpa passphrase */ 557 | memset(settings->wifi_pass, 0, 558 | sizeof(settings->wifi_pass)); 559 | strncpy(settings->wifi_pass, cmd + 5, 560 | sizeof(settings->wifi_pass)); 561 | 562 | WiFi.disconnect(); 563 | if (settings->wifi_ssid[0]) 564 | WiFi.begin(settings->wifi_ssid, 565 | settings->wifi_pass); 566 | } else if (strcmp(lcmd, "pass?") == 0) { 567 | /* AT$PASS?: print wep/wpa passphrase */ 568 | outputf("\n%s\r\n", settings->wifi_pass); 569 | } else if (strcmp(lcmd, "pins?") == 0) { 570 | /* AT$PINS?: watch MCP23S18 lines for debugging */ 571 | uint16_t prev = UINT16_MAX; 572 | int i, done = 0; 573 | unsigned char b, bit, n, data = 0; 574 | extern MCP23S18 mcp; 575 | 576 | ms_datadir(INPUT); 577 | 578 | outputf("\n"); 579 | 580 | while (!done) { 581 | ESP.wdtFeed(); 582 | 583 | /* watch for ^C */ 584 | if (Serial.available()) { 585 | switch (b = Serial.read()) { 586 | case 3: 587 | /* ^C */ 588 | done = 1; 589 | break; 590 | case 'd': 591 | Serial.printf("data input\r\n"); 592 | ms_datadir(INPUT); 593 | break; 594 | case 'D': 595 | Serial.printf("data output\r\n"); 596 | ms_datadir(OUTPUT); 597 | break; 598 | case 'L': 599 | Serial.printf("linefeed high\r\n"); 600 | mcp.digitalWrite(pLineFeed, HIGH); 601 | break; 602 | case 'l': 603 | Serial.printf("linefeed low\r\n"); 604 | mcp.digitalWrite(pLineFeed, LOW); 605 | break; 606 | case 'S': 607 | Serial.printf("strobe high\r\n"); 608 | mcp.digitalWrite(pStrobe, HIGH); 609 | break; 610 | case 's': 611 | Serial.printf("strobe low\r\n"); 612 | mcp.digitalWrite(pStrobe, LOW); 613 | break; 614 | case '0': 615 | case '1': 616 | case '2': 617 | case '3': 618 | case '4': 619 | case '5': 620 | case '6': 621 | case '7': 622 | n = (b - '0'); 623 | bit = (data & (1 << n)); 624 | if (bit) 625 | data &= ~(1 << n); 626 | else 627 | data |= (1 << n); 628 | Serial.printf("turning data%d " 629 | "%s (0x%x)\r\n", 630 | n, bit ? "off" : " on", 631 | data); 632 | ms_datadir(OUTPUT); 633 | ms_writedata(data); 634 | break; 635 | } 636 | } 637 | 638 | uint16_t all = ms_status(); 639 | if (all != prev) { 640 | Serial.print("data: "); 641 | for (i = 0; i < 8; i++) 642 | Serial.print((all & (1 << i)) ? 643 | '1' : '0'); 644 | 645 | Serial.print(" status: "); 646 | for (; i < 16; i++) 647 | Serial.print((all & (1 << i)) ? 648 | '1' : '0'); 649 | Serial.print("\r\n"); 650 | prev = all; 651 | } 652 | } 653 | ms_datadir(INPUT); 654 | did_nl = true; 655 | } else if (strncmp(lcmd, "sb=", 3) == 0) { 656 | uint32_t baud = 0; 657 | int chars = 0; 658 | 659 | /* AT$SB=...: set baud rate */ 660 | if (sscanf(lcmd, "sb=%d%n", &baud, &chars) != 1 || 661 | chars == 0) { 662 | if (settings->verbal) 663 | output("ERROR invalid baud rate\r\n"); 664 | else 665 | output("4\r"); 666 | break; 667 | } 668 | 669 | switch (baud) { 670 | case 110: 671 | case 300: 672 | case 1200: 673 | case 2400: 674 | case 4800: 675 | case 9600: 676 | case 19200: 677 | case 38400: 678 | case 57600: 679 | case 115200: 680 | settings->baud = baud; 681 | if (!settings->quiet) { 682 | if (settings->verbal) 683 | outputf("\nOK switching to " 684 | "%d\r\n", settings->baud); 685 | else 686 | output("0\r"); 687 | } 688 | Serial.flush(); 689 | Serial.begin(settings->baud); 690 | break; 691 | default: 692 | output("ERROR unsupported baud rate\r\n"); 693 | break; 694 | } 695 | } else if (strcmp(lcmd, "sb?") == 0) { 696 | /* AT$SB?: print baud rate */ 697 | outputf("\n%d\r\n", settings->baud); 698 | did_nl = true; 699 | } else if (strcmp(lcmd, "scan") == 0) { 700 | /* AT$SCAN: scan for wifi networks */ 701 | int n = WiFi.scanNetworks(); 702 | 703 | /* don't scroll off the screen */ 704 | if (n > 14) 705 | n = 14; 706 | 707 | output("\n"); 708 | 709 | for (int i = 0; i < n; i++) { 710 | outputf("%02d: %s (chan %d, %ddBm, ", 711 | i + 1, 712 | WiFi.SSID(i).c_str(), 713 | WiFi.channel(i), 714 | WiFi.RSSI(i)); 715 | 716 | switch (WiFi.encryptionType(i)) { 717 | case ENC_TYPE_WEP: 718 | output("WEP"); 719 | break; 720 | case ENC_TYPE_TKIP: 721 | output("WPA-PSK"); 722 | break; 723 | case ENC_TYPE_CCMP: 724 | output("WPA2-PSK"); 725 | break; 726 | case ENC_TYPE_NONE: 727 | output("NONE"); 728 | break; 729 | case ENC_TYPE_AUTO: 730 | output("WPA-PSK/WPA2-PSK"); 731 | break; 732 | default: 733 | outputf("?(%d)", 734 | WiFi.encryptionType(i)); 735 | } 736 | 737 | output(")\r\n"); 738 | } 739 | did_nl = true; 740 | } else if (strncmp(lcmd, "ssid=", 5) == 0) { 741 | /* AT$SSID=...: set wifi ssid */ 742 | memset(settings->wifi_ssid, 0, 743 | sizeof(settings->wifi_ssid)); 744 | strncpy(settings->wifi_ssid, cmd + 5, 745 | sizeof(settings->wifi_ssid)); 746 | 747 | WiFi.disconnect(); 748 | if (settings->wifi_ssid[0]) 749 | WiFi.begin(settings->wifi_ssid, 750 | settings->wifi_pass); 751 | } else if (strcmp(lcmd, "ssid?") == 0) { 752 | /* AT$SSID?: print wifi ssid */ 753 | outputf("\n%s\r\n", settings->wifi_ssid); 754 | did_nl = true; 755 | } else if (strncmp(lcmd, "syslog=", 7) == 0) { 756 | /* AT$SYSLOG=...: set syslog server */ 757 | memset(settings->syslog_server, 0, 758 | sizeof(settings->syslog_server)); 759 | strncpy(settings->syslog_server, cmd + 7, 760 | sizeof(settings->syslog_server)); 761 | syslog_setup(); 762 | syslog.logf(LOG_INFO, "syslog server changed to %s", 763 | settings->syslog_server); 764 | } else if (strcmp(lcmd, "syslog?") == 0) { 765 | /* AT$SYSLOG?: print syslog server */ 766 | outputf("\n%s\r\n", settings->syslog_server); 767 | did_nl = true; 768 | } else if (strncmp(lcmd, "tts=", 4) == 0) { 769 | /* AT$TTS=: set telnet NAWS */ 770 | int w, h, chars; 771 | if (sscanf(lcmd + 4, "%dx%d%n", &w, &h, &chars) == 2 && 772 | chars > 0) { 773 | if (w < 1 || w > 255) { 774 | errstr = strdup("invalid width"); 775 | goto error; 776 | } 777 | if (h < 1 || h > 255) { 778 | errstr = strdup("invalid height"); 779 | goto error; 780 | } 781 | 782 | settings->telnet_tts_w = w; 783 | settings->telnet_tts_h = h; 784 | } else { 785 | errstr = strdup("must be WxH"); 786 | goto error; 787 | } 788 | } else if (strcmp(lcmd, "tts?") == 0) { 789 | /* AT$TTS?: show telnet NAWS setting */ 790 | outputf("\n%dx%d\r\n", settings->telnet_tts_w, 791 | settings->telnet_tts_h); 792 | } else if (strncmp(lcmd, "tty=", 4) == 0) { 793 | /* AT$TTY=: set telnet TTYPE */ 794 | memset(settings->telnet_tterm, 0, 795 | sizeof(settings->telnet_tterm)); 796 | strncpy(settings->telnet_tterm, cmd + 4, 797 | sizeof(settings->telnet_tterm)); 798 | } else if (strcmp(lcmd, "at$tty?") == 0) { 799 | /* AT$TTY?: show telnet TTYPE setting */ 800 | outputf("%s\r\nOK\r\n", settings->telnet_tterm); 801 | } else if (strncmp(lcmd, "update?", 7) == 0) { 802 | /* AT$UPDATE?: show whether an OTA update is available */ 803 | char *url = NULL; 804 | if (strncmp(lcmd, "update? http", 12) == 0) 805 | url = lcmd + 8; 806 | update_process(url, false, false); 807 | did_response = true; 808 | } else if (strncmp(lcmd, "update!", 7) == 0) { 809 | /* AT$UPDATE!: force an OTA update */ 810 | char *url = NULL; 811 | if (strncmp(lcmd, "update! http", 12) == 0) 812 | url = lcmd + 8; 813 | update_process(url, true, true); 814 | did_response = true; 815 | } else if (strcmp(lcmd, "update") == 0 || 816 | strncmp(lcmd, "update http", 11) == 0) { 817 | /* AT$UPDATE: do an OTA update */ 818 | char *url = NULL; 819 | if (strncmp(lcmd, "update http", 11) == 0) 820 | url = lcmd + 7; 821 | update_process(url, true, false); 822 | did_response = true; 823 | } else if (strncmp(lcmd, "upload", 6) == 0) { 824 | /* AT$UPLOAD: mailstation program loader */ 825 | unsigned int bytes = 0; 826 | unsigned char b; 827 | 828 | if (sscanf(lcmd, "upload %u", &bytes) != 1 || 829 | bytes < 1) 830 | goto error; 831 | 832 | if (bytes > (MAX_UPLOAD_SIZE - 1)) { 833 | outputf("\nERROR size cannot be larger than " 834 | "%d\r\n", (MAX_UPLOAD_SIZE - 1)); 835 | break; 836 | } 837 | 838 | /* 839 | * Prevent output() from sending data to the 840 | * MailStation until we see it on the other side of the 841 | * upload. 842 | */ 843 | mailstation_alive = false; 844 | 845 | /* 846 | * Send low and high bytes of size. 847 | * 848 | * XXX: Tell the MailStation we're sending one more 849 | * byte than we're receiving from sendload so we can 850 | * include one trailing null byte because sometimes the 851 | * final ack of ms_write will fail to see ack line go 852 | * low before WSLoader jumps to the payload. 853 | * Figure out why that happens and remove this hack. 854 | */ 855 | if (ms_write((bytes + 1) & 0xff) != 0 || 856 | ms_write(((bytes + 1) >> 8) & 0xff) != 0) { 857 | output("ERROR MailStation failed to receive " 858 | "size\r\n"); 859 | break; 860 | } 861 | 862 | outputf("\nOK send your %d byte%s\r\n", bytes, 863 | bytes == 1 ? "" : "s"); 864 | 865 | t = millis(); 866 | int written = 0; 867 | char cksum = 0; 868 | while (bytes > 0) { 869 | if (!Serial.available()) { 870 | if (millis() - t > 5000) 871 | break; 872 | yield(); 873 | continue; 874 | } 875 | 876 | b = Serial.read(); 877 | t = millis(); 878 | 879 | if (ms_write(b) != 0) 880 | break; 881 | 882 | cksum ^= b; 883 | written++; 884 | bytes--; 885 | 886 | if (written % 32 == 0) 887 | output(cksum); 888 | } 889 | 890 | if (bytes == 0) { 891 | output(cksum); 892 | output("\r\nOK good luck\r\n"); 893 | /* XXX: trailing dummy byte, ignore response */ 894 | ms_write(0); 895 | } else 896 | outputf("\r\nERROR MailStation failed to " 897 | "receive byte with %d byte%s left\r\n", 898 | bytes, (bytes == 1 ? "" : "s")); 899 | 900 | did_nl = true; 901 | break; 902 | } else 903 | goto error; 904 | 905 | /* consume all chars */ 906 | len = 0; 907 | break; 908 | case '&': 909 | if (cmd[0] == '\0') 910 | goto error; 911 | 912 | cmd_char = lcmd[0]; 913 | len--; 914 | cmd++; 915 | lcmd++; 916 | 917 | /* find optional single digit after &command, defaulting to 0 */ 918 | cmd_num = 0; 919 | if (cmd[0] >= '0' && cmd[0] <= '9') { 920 | if (cmd[1] >= '0' && cmd[1] <= '9') 921 | /* nothing uses more than 1 digit */ 922 | goto error; 923 | cmd_num = cmd[0] - '0'; 924 | len--; 925 | cmd++; 926 | lcmd++; 927 | } 928 | 929 | switch (cmd_char) { 930 | case 'w': 931 | switch (cmd_num) { 932 | case 0: 933 | /* AT&W: save settings */ 934 | /* force setting dirty */ 935 | (void)EEPROM.getDataPtr(); 936 | if (!EEPROM.commit()) 937 | goto error; 938 | break; 939 | default: 940 | goto error; 941 | } 942 | break; 943 | case 'z': { 944 | /* AT&Z: manage bookmarks */ 945 | uint32_t index = 0; 946 | uint8_t query; 947 | int chars = 0; 948 | 949 | if (sscanf(lcmd, "%u=%n", &index, &chars) == 1 && 950 | chars > 0) { 951 | /* AT&Zn=...: store address */ 952 | query = 0; 953 | } else if (sscanf(lcmd, "%u?%n", &index, &chars) == 1 && 954 | chars > 0) { 955 | /* AT&Zn?: query stored address */ 956 | query = 1; 957 | } else { 958 | errstr = strdup("invalid store command"); 959 | goto error; 960 | } 961 | 962 | if (index < 1 || index > NUM_BOOKMARKS) { 963 | errstr = strdup("invalid index"); 964 | goto error; 965 | } 966 | 967 | if (query) { 968 | outputf("\n%s\r\n", 969 | settings->bookmarks[index - 1]); 970 | } else { 971 | memset(settings->bookmarks[index - 1], 0, 972 | sizeof(settings->bookmarks[0])); 973 | strncpy(settings->bookmarks[index - 1], 974 | cmd + 2, 975 | sizeof(settings->bookmarks[0]) - 1); 976 | } 977 | break; 978 | } 979 | default: 980 | goto error; 981 | } 982 | 983 | /* consume all chars */ 984 | len = 0; 985 | break; 986 | default: 987 | goto error; 988 | } 989 | 990 | done_parsing: 991 | /* if any len left, parse as another command */ 992 | if (len > 0) 993 | goto parse_cmd; 994 | 995 | if (olcmd) 996 | free(olcmd); 997 | 998 | if (!did_response && state == STATE_AT && !settings->quiet) { 999 | if (settings->verbal) 1000 | outputf("%sOK\r\n", did_nl ? "" : "\n"); 1001 | else 1002 | output("0\r"); 1003 | } 1004 | 1005 | return; 1006 | 1007 | error: 1008 | if (olcmd) 1009 | free(olcmd); 1010 | 1011 | if (!did_response && !settings->quiet) { 1012 | if (settings->verbal) { 1013 | output("\nERROR"); 1014 | if (errstr != NULL) 1015 | outputf(" %s", errstr); 1016 | output("\r\n"); 1017 | } else 1018 | output("4\r"); 1019 | } 1020 | 1021 | if (errstr != NULL) 1022 | free(errstr); 1023 | } 1024 | -------------------------------------------------------------------------------- /host/GNUmakefile: -------------------------------------------------------------------------------- 1 | CFLAGS += -Wall 2 | 3 | PREFIX ?= /usr/local 4 | 5 | all: sendload 6 | 7 | clean: 8 | rm -f sendload 9 | 10 | install: sendload 11 | install -S $< $(PREFIX)/bin/$< 12 | 13 | sendload: sendload.c 14 | $(CC) $(CFLAGS) -o $@ $< 15 | -------------------------------------------------------------------------------- /host/sendload.c: -------------------------------------------------------------------------------- 1 | /* 2 | * WiFiStation 3 | * Serial port program loader 4 | * 5 | * Copyright (c) 2019-2021 joshua stein 6 | * 7 | * Permission to use, copy, modify, and distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | static int debug = 0; 35 | static char vbuf[512]; 36 | static int serial_fd = -1; 37 | 38 | void 39 | usage(void) 40 | { 41 | fprintf(stderr, "usage: %s [-d] [-s serial speed] " 42 | "\n", 43 | getprogname()); 44 | exit(1); 45 | } 46 | 47 | void 48 | serial_setup(int fd, speed_t speed) 49 | { 50 | struct termios tty; 51 | 52 | if (ioctl(fd, TIOCEXCL) != 0) 53 | err(1, "ioctl(TIOCEXCL)"); 54 | if (tcgetattr(fd, &tty) < 0) 55 | err(1, "tcgetattr"); 56 | 57 | tty.c_cflag |= (CLOCAL | CREAD); /* ignore modem controls */ 58 | tty.c_cflag &= ~CSIZE; 59 | tty.c_cflag |= CS8; /* 8-bit characters */ 60 | tty.c_cflag &= ~PARENB; /* no parity bit */ 61 | tty.c_cflag &= ~CSTOPB; /* only need 1 stop bit */ 62 | 63 | /* setup for non-canonical mode */ 64 | tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); 65 | tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); 66 | tty.c_oflag &= ~OPOST; 67 | 68 | /* fetch bytes as they become available */ 69 | tty.c_cc[VMIN] = 1; 70 | tty.c_cc[VTIME] = 1; 71 | 72 | cfsetspeed(&tty, speed); 73 | 74 | if (tcsetattr(fd, TCSAFLUSH, &tty) != 0) 75 | err(1, "tcsetattr"); 76 | } 77 | 78 | size_t 79 | serial_read(void *buf, size_t nbytes) 80 | { 81 | size_t ret; 82 | 83 | ret = read(serial_fd, buf, nbytes); 84 | 85 | if (debug && ret > 0) { 86 | strvisx(vbuf, buf, ret, VIS_NL | VIS_CSTYLE | VIS_OCTAL); 87 | printf("<<< %s\n", vbuf); 88 | } 89 | 90 | return ret; 91 | } 92 | 93 | size_t 94 | serial_write(const void *buf, size_t nbytes) 95 | { 96 | if (debug) { 97 | strvisx(vbuf, buf, nbytes, VIS_NL | VIS_CSTYLE | VIS_OCTAL); 98 | printf(">>> %s\n", vbuf); 99 | } 100 | 101 | return write(serial_fd, buf, nbytes); 102 | } 103 | 104 | int 105 | main(int argc, char *argv[]) 106 | { 107 | FILE *pFile; 108 | struct stat sb; 109 | struct pollfd pfd[1]; 110 | char *fn, *serial_dev = NULL; 111 | unsigned char buf[128], b, cksum = 0, rcksum; 112 | unsigned int sent = 0, size = 0; 113 | int len, rlen, ch; 114 | int serial_speed = B115200; 115 | 116 | while ((ch = getopt(argc, argv, "ds:")) != -1) { 117 | switch (ch) { 118 | case 'd': 119 | debug = 1; 120 | break; 121 | case 's': 122 | serial_speed = (unsigned)strtol(optarg, NULL, 0); 123 | if (errno) 124 | err(1, "invalid serial port speed value"); 125 | break; 126 | default: 127 | usage(); 128 | } 129 | } 130 | argc -= optind; 131 | argv += optind; 132 | 133 | if (argc != 2) 134 | usage(); 135 | 136 | serial_dev = argv[0]; 137 | serial_fd = open(serial_dev, O_RDWR|O_SYNC); 138 | if (serial_fd < 0) 139 | err(1, "open %s", serial_dev); 140 | 141 | serial_setup(serial_fd, serial_speed); 142 | 143 | fn = argv[1]; 144 | pFile = fopen(fn, "rb"); 145 | if (!pFile) 146 | err(1, "open %s", fn); 147 | 148 | if (fstat(fileno(pFile), &sb) != 0) 149 | err(1, "fstat %s", fn); 150 | 151 | /* we're never going to send huge files */ 152 | size = (unsigned int)sb.st_size; 153 | 154 | if (debug) 155 | printf("sending %s (%d bytes) via %s\n", fn, size, serial_dev); 156 | 157 | /* 158 | * spam some newlines since the TTL connection kinda sucks, and ^C in 159 | * case the device is in AT$PINS? mode 160 | */ 161 | serial_write("\r\n\r\n", 4); 162 | b = 3; 163 | serial_write(&b, 1); 164 | 165 | /* 166 | * send AT to get some output since sometimes the first character is 167 | * lost and we'll just get 'T', and we need to see a full response to 168 | * AT$UPLOAD later 169 | */ 170 | serial_write("AT\r", 3); 171 | pfd[0].fd = serial_fd; 172 | pfd[0].events = POLLIN; 173 | while (poll(pfd, 1, 100) > 0) 174 | serial_read(buf, sizeof(buf)); 175 | 176 | len = snprintf(buf, sizeof(buf), "AT$UPLOAD %d\r", size); 177 | serial_write(buf, len); 178 | memset(buf, 0, sizeof(buf)); 179 | 180 | /* it will echo, along with an OK */ 181 | rlen = 0; 182 | while (poll(pfd, 1, 100) > 0) { 183 | len = serial_read(buf + rlen, sizeof(buf) - rlen); 184 | if (sizeof(buf) - rlen <= 0) 185 | break; 186 | rlen += len; 187 | } 188 | 189 | len = 0; 190 | if (sscanf(buf, "AT$UPLOAD %d\r\nOK%n", &rlen, &len) != 1 || len < 10) { 191 | strvis(vbuf, buf, VIS_NL | VIS_CSTYLE | VIS_OCTAL); 192 | errx(1, "bad response to AT$UPLOAD: %s", vbuf); 193 | } 194 | 195 | /* clear out any remaining response so we can read each byte echoed */ 196 | pfd[0].fd = serial_fd; 197 | pfd[0].events = POLLIN; 198 | while (poll(pfd, 1, 100) > 0) 199 | serial_read(buf, sizeof(buf)); 200 | 201 | while (sent < size) { 202 | b = fgetc(pFile); 203 | if (debug) 204 | printf("sending: %05d/%05d (0x%x)\n", sent, size, b); 205 | serial_write(&b, 1); 206 | cksum ^= b; 207 | sent++; 208 | 209 | if (sent % 32 == 0 || sent == size) { 210 | if (poll(pfd, 1, 1000) < 1) { 211 | printf("\n"); 212 | errx(1, "failed poll at byte %d/%d", sent, 213 | size); 214 | } 215 | 216 | if (serial_read(&rcksum, 1) != 1) { 217 | printf("\n"); 218 | errx(1, "failed read at byte %d/%d", sent, 219 | size); 220 | } 221 | 222 | if (rcksum == cksum) { 223 | if (debug) 224 | printf("checksum 0x%x matches\n", 225 | (cksum & 0xff)); 226 | } else { 227 | printf("\n"); 228 | errx(1, "failed checksum of byte %d/%d " 229 | "(expected 0x%x, received 0x%x)", 230 | sent, size, cksum, rcksum); 231 | } 232 | } 233 | 234 | if (!debug) 235 | printf("\rsent: %05d/%05d", sent, size); 236 | 237 | fflush(stdout); 238 | } 239 | fclose(pFile); 240 | 241 | printf("\n"); 242 | 243 | /* wait for our final OK */ 244 | while (poll(pfd, 1, 200) > 0) 245 | serial_read(buf, sizeof(buf)); 246 | 247 | close(serial_fd); 248 | 249 | return 0; 250 | } 251 | -------------------------------------------------------------------------------- /mailstation/GNUmakefile: -------------------------------------------------------------------------------- 1 | # pkg_add sdcc 2 | 3 | ASZ80 ?= sdasz80 -l 4 | SDCC ?= sdcc -mz80 5 | 6 | CFLAGS += -Wall 7 | 8 | UNAME_S := $(shell uname -s) 9 | 10 | OBJCOPY ?= objcopy 11 | ifeq ($(UNAME_S),Darwin) 12 | OBJCOPY = /opt/homebrew/Cellar/llvm/*/bin/llvm-objcopy 13 | endif 14 | 15 | all: wsloader.bin flashloader.bin 16 | 17 | clean: 18 | rm -f *.{bin,ihx,lk,lst,map,noi,rel} 19 | 20 | # parallel loader 21 | wsloader.rel: wsloader.asm 22 | $(ASZ80) -o $@ $< 23 | 24 | wsloader.ihx: wsloader.rel 25 | $(SDCC) --no-std-crt0 -o $@ $< 26 | 27 | wsloader.bin: wsloader.ihx 28 | $(OBJCOPY) -Iihex -Obinary $< $@ 29 | hexdump -C $@ 30 | 31 | # dataflash loader 32 | flashloader.rel: flashloader.asm 33 | $(ASZ80) -o $@ $< 34 | 35 | flashloader.ihx: flashloader.rel 36 | $(SDCC) --no-std-crt0 -o $@ $< 37 | 38 | flashloader.bin: flashloader.ihx 39 | $(OBJCOPY) -Iihex -Obinary $< $@ 40 | -------------------------------------------------------------------------------- /mailstation/flashloader.asm: -------------------------------------------------------------------------------- 1 | ; vim:syntax=z8a:ts=8 2 | ; 3 | ; WiFiStation 4 | ; DataFlash loader 5 | ; 6 | ; Copyright (c) 2019-2021 joshua stein 7 | ; 8 | ; Permission to use, copy, modify, and distribute this software for any 9 | ; purpose with or without fee is hereby granted, provided that the above 10 | ; copyright notice and this permission notice appear in all copies. 11 | ; 12 | ; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | ; WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | ; MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15 | ; ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | ; WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | ; ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 18 | ; OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | ; 20 | 21 | .module dataflashloader 22 | 23 | ; locations relative to v2.54 firmware 24 | .equ p2shadow, #0xdba2 25 | .equ delay_func, #0x0a5c 26 | 27 | ; which page (Yahoo! menu slot) to save an uploaded program to 28 | ; page 0 is 0x0000, page 1 0x4000, page 2, 0x8000, etc. 29 | .equ DATAFLASH_PAGE, #0 30 | 31 | ; when running from WSLoader, we are loaded at 0x8000 and use slot 4 32 | .equ SLOT_ADDR, #0x4000 33 | .equ RUN_ADDR, #0x8000 34 | .equ SLOT_DEVICE, #0x6 35 | .equ SLOT_PAGE, #0x5 36 | 37 | ; where we'll buffer the 256 bytes we receive before writing to flash 38 | .equ RAM_ADDR, #0xd000 39 | 40 | .equ SDP_LOCK, #SLOT_ADDR + 0x040a 41 | .equ SDP_UNLOCK, #SLOT_ADDR + 0x041a 42 | 43 | .equ CONTROL_DIR, #0x0a 44 | .equ CONTROL_DIR_OUT, #0xff 45 | .equ CONTROL_DIR_IN, #0 46 | 47 | .equ CONTROL_PORT, #0x9 48 | .equ CONTROL_STROBE, #(1 << 0) 49 | .equ CONTROL_LINEFEED, #(1 << 1) 50 | .equ CONTROL_INIT, #(1 << 2) 51 | .equ CONTROL_SELECT, #(1 << 3) 52 | 53 | .equ DATA_DIR, #0x2c 54 | .equ DATA_DIR_OUT, #0xff 55 | .equ DATA_DIR_IN, #0 56 | .equ DATA_PORT, #0x2d 57 | 58 | .equ STATUS_PORT, #0x21 59 | .equ STATUS_BUSY, #(1 << 7) 60 | .equ STATUS_ACK, #(1 << 6) 61 | .equ STATUS_PAPEROUT, #(1 << 5) 62 | 63 | 64 | .area _DATA 65 | .area _HEADER (ABS) 66 | .org #RUN_ADDR 67 | 68 | jp main 69 | 70 | .dw (icons) 71 | .dw (caption) 72 | .dw (dunno) 73 | dunno: 74 | .db #0 75 | zip: 76 | .dw #0 77 | zilch: 78 | .dw #0 79 | caption: 80 | .dw #0x0001 81 | .dw (endcap - caption - 6) ; num of chars 82 | .dw #0x0006 ; offset to first char 83 | .ascii "FlashLoader" ; the caption string 84 | endcap: 85 | 86 | icons: 87 | .dw #0x0 ; size icon0 88 | .dw (icon0 - icons) ; offset to icon0 89 | .dw #0 ; size icon1 90 | .dw (icon1 - icons) ; offset to icon1 (0x00b5) 91 | icon0: 92 | .dw #0x0 ; icon width 93 | .db #0x0 ; icon height 94 | 95 | icon1: 96 | .dw #0 ; icon width 97 | .db #0 ; icon height 98 | 99 | ; enables 'new mail' light when a is 1, else disables 100 | new_mail: 101 | di 102 | cp #0 103 | jr z, light_off 104 | light_on: 105 | ld a, (p2shadow) 106 | set 4, a 107 | jr write_p2 108 | light_off: 109 | ld a, (p2shadow) 110 | res 4, a 111 | write_p2: 112 | ld (p2shadow), a 113 | out (#0x02), a ; write p2shadow to port2 114 | ei 115 | ret 116 | 117 | delay: 118 | push af 119 | push bc 120 | push hl 121 | call #delay_func 122 | pop hl 123 | pop bc 124 | pop af 125 | ret 126 | 127 | ; get transfer size bytes, then read a flash sector into ram, write it, repeat 128 | main: 129 | push ix 130 | ld ix, #0 131 | add ix, sp 132 | push bc 133 | push de 134 | push hl 135 | ld hl, #5000 136 | push hl 137 | call delay ; wait 5 seconds before starting in 138 | pop hl ; case wifistation is rebooting 139 | ld a, #CONTROL_DIR_OUT 140 | out (#CONTROL_DIR), a 141 | xor a 142 | out (#CONTROL_PORT), a 143 | ld a, #DATA_DIR_IN 144 | out (#DATA_DIR), a ; we're going to be receiving 145 | ld a, #1 ; enable 'new mail' light 146 | call new_mail 147 | ld hl, #-8 ; stack bytes for local storage 148 | add hl, sp 149 | ld sp, hl 150 | in a, (#SLOT_DEVICE) 151 | ld -3(ix), a ; stack[-3] = slot 8 device 152 | in a, (#SLOT_PAGE) 153 | ld -4(ix), a ; stack[-4] = slot 8 device 154 | ld a, #3 ; slot 8 device = dataflash 155 | out (#SLOT_DEVICE), a 156 | xor a ; slot 8 page = 0 157 | out (#SLOT_PAGE), a 158 | ld hl, #SLOT_ADDR 159 | ld -5(ix), h 160 | ld -6(ix), l ; stack[-5,-6] = data flash location 161 | get_size: 162 | call lptrecv_blocking ; low byte of total bytes to download 163 | ld -8(ix), a 164 | call lptrecv_blocking ; high byte of total bytes to download 165 | ld -7(ix), a 166 | cp #0x40 ; we can't write more than 0x3fff 167 | jr c, size_ok 168 | size_too_big: 169 | jp 0x0000 ; *shrug* 170 | size_ok: 171 | di ; prevent things from touching RAM 172 | call sdp 173 | ld a, (#SDP_UNLOCK) 174 | read_chunk_into_ram: 175 | ; read 256 bytes at a time into ram, erase the target flash sector, 176 | ; then program it with those bytes 177 | ld b, -7(ix) 178 | ld c, -8(ix) 179 | ld hl, #RAM_ADDR 180 | ingest_loop: 181 | call lptrecv_blocking 182 | ld (hl), a 183 | inc hl 184 | dec bc 185 | ld a, l 186 | cp #0 187 | jr z, done_ingesting ; on 256-byte boundary 188 | ld a, b 189 | cp #0 190 | jr nz, ingest_loop ; bc != 0, keep reading input 191 | ld a, c 192 | cp #0 193 | jr nz, ingest_loop ; bc != 0, keep reading input 194 | done_ingesting: 195 | ld -7(ix), b ; update bytes remaining to fetch on 196 | ld -8(ix), c ; next iteration 197 | move_into_flash: 198 | ld a, #3 ; slot 8 device = dataflash 199 | out (#SLOT_DEVICE), a 200 | ld a, #DATAFLASH_PAGE 201 | out (#SLOT_PAGE), a 202 | ld de, #RAM_ADDR 203 | ld h, -5(ix) ; data flash write location 204 | ld l, -6(ix) 205 | sector_erase: 206 | ld (hl), #0x20 ; 28SF040 Sector-Erase Setup 207 | ld (hl), #0xd0 ; 28SF040 Execute 208 | sector_erase_wait: 209 | ld a, (hl) ; wait until End-of-Write 210 | ld b, a 211 | ld a, (hl) 212 | cp b 213 | jr nz, sector_erase_wait 214 | byte_program_loop: 215 | ld a, (de) 216 | ld (hl), #0x10 ; 28SF040 Byte-Program Setup 217 | ld (hl), a ; 28SF040 Execute 218 | byte_program: 219 | ld a, (hl) 220 | ld b, a 221 | ld a, (hl) ; End-of-Write by reading it 222 | cp b 223 | jr nz, byte_program ; read until writing succeeds 224 | inc hl ; next flash byte 225 | inc de ; next RAM byte 226 | ld a, l 227 | cp #0 228 | jr nz, byte_program_loop 229 | sector_done: 230 | ld -5(ix), h ; update data flash write location 231 | ld -6(ix), l 232 | ld a, -7(ix) 233 | cp #0 234 | jp nz, read_chunk_into_ram ; more data to transfer 235 | ld a, -8(ix) 236 | cp #0 237 | jp nz, read_chunk_into_ram ; more data to transfer 238 | ; all done 239 | flash_out: 240 | ld a, #3 ; slot 8 device = dataflash 241 | out (#SLOT_DEVICE), a 242 | xor a ; slot 8 page = 0 243 | out (#SLOT_PAGE), a 244 | call sdp 245 | ld a, (#SDP_LOCK) 246 | bail: 247 | ld a, -3(ix) ; restore slot 8 device 248 | out (#SLOT_DEVICE), a 249 | ld a, -4(ix) ; restore slot 8 page 250 | out (#SLOT_PAGE), a 251 | ld hl, #8 ; remove stack bytes 252 | add hl, sp 253 | ld sp, hl 254 | pop hl 255 | pop de 256 | pop bc 257 | ld sp, ix 258 | pop ix 259 | ei 260 | jp 0x0000 ; reset when we're done 261 | 262 | 263 | sdp: 264 | ld a, (#SLOT_ADDR + 0x1823) ; 28SF040 Software Data Protection 265 | ld a, (#SLOT_ADDR + 0x1820) 266 | ld a, (#SLOT_ADDR + 0x1822) 267 | ld a, (#SLOT_ADDR + 0x0418) 268 | ld a, (#SLOT_ADDR + 0x041b) 269 | ld a, (#SLOT_ADDR + 0x0419) 270 | ret 271 | ; caller needs to read final SDP_LOCK or SDP_UNLOCK address 272 | 273 | 274 | ; return byte in a 275 | lptrecv_blocking: 276 | push hl 277 | wait_for_busy: 278 | in a, (#STATUS_PORT) 279 | and #STATUS_BUSY ; is busy high? (strobe on writer) 280 | jr z, wait_for_busy ; no, wait until it is 281 | and #STATUS_ACK ; but is ack high too? that's bogus 282 | jr nz, wait_for_busy 283 | ld a, #CONTROL_LINEFEED ; raise linefeed 284 | out (#CONTROL_PORT), a 285 | wait_for_busy_ack: 286 | in a, (#STATUS_PORT) 287 | and #STATUS_BUSY ; is busy high? 288 | jr nz, wait_for_busy_ack ; no, wait 289 | read_data: 290 | in a, (#DATA_PORT) 291 | ld l, a 292 | lower_lf: 293 | xor a 294 | out (#CONTROL_PORT), a ; lower linefeed 295 | ld a, l ; return read byte in a 296 | pop hl 297 | ret 298 | -------------------------------------------------------------------------------- /mailstation/wsloader.asm: -------------------------------------------------------------------------------- 1 | ; vim:syntax=z8a:ts=8 2 | ; 3 | ; WiFiStation 4 | ; Parallel port loader 5 | ; 6 | ; Copyright (c) 2019-2021 joshua stein 7 | ; 8 | ; Permission to use, copy, modify, and distribute this software for any 9 | ; purpose with or without fee is hereby granted, provided that the above 10 | ; copyright notice and this permission notice appear in all copies. 11 | ; 12 | ; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | ; WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | ; MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15 | ; ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | ; WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | ; ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 18 | ; OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | ; 20 | 21 | .module loader 22 | 23 | .area _DATA 24 | .area _HEADER (ABS) 25 | .org 0x4000 ; we're running from dataflash 26 | 27 | jp main 28 | 29 | .dw (icons) 30 | .dw (caption) 31 | .dw (dunno) 32 | dunno: 33 | .db #0 34 | .dw #0 35 | .dw #0 36 | caption: 37 | .dw #0x0001 38 | .dw (endcap - caption - 6) 39 | .dw #0x0006 40 | .ascii "WSLoader" ; the icon caption 41 | endcap: 42 | 43 | icons: 44 | .dw #0 ; size icon0 45 | .dw (icon0 - icons) ; offset to icon0 46 | .dw #0 ; size icon1 47 | .dw (icon1 - icons) ; offset to icon1 (0x00b5) 48 | icon0: 49 | .dw #0 ; icon width 50 | .db #0 ; icon height 51 | icon1: 52 | .dw #0 ; icon width 53 | .db #0 ; icon height 54 | 55 | ; actual loader code 56 | 57 | .equ CONTROL_DIR, #0x0a 58 | .equ CONTROL_DIR_OUT, #0xff 59 | .equ CONTROL_DIR_IN, #0 60 | 61 | .equ CONTROL_PORT, #0x9 62 | .equ CONTROL_STROBE, #(1 << 0) 63 | .equ CONTROL_LINEFEED, #(1 << 1) 64 | .equ CONTROL_INIT, #(1 << 2) 65 | .equ CONTROL_SELECT, #(1 << 3) 66 | 67 | .equ DATA_DIR, #0x2c 68 | .equ DATA_DIR_OUT, #0xff 69 | .equ DATA_DIR_IN, #0 70 | .equ DATA_PORT, #0x2d 71 | 72 | .equ STATUS_PORT, #0x21 73 | .equ STATUS_BUSY, #(1 << 7) 74 | .equ STATUS_ACK, #(1 << 6) 75 | 76 | main: 77 | ; lower control lines 78 | ld a, #CONTROL_DIR_OUT 79 | out (#CONTROL_DIR), a 80 | xor a 81 | out (#CONTROL_PORT), a 82 | ld a, #DATA_DIR_IN 83 | out (#DATA_DIR), a ; we're going to be receiving 84 | 85 | ; first read the low and high bytes of the length we're going to read 86 | call lptrecv_blocking 87 | ld l, a 88 | call lptrecv_blocking 89 | ld h, a ; hl = bytes to download 90 | 91 | ld a, #1 ; put ram page 1 into slot8000 92 | out (#0x08), a 93 | out (#0x07), a 94 | 95 | ld bc, #0x8000 ; bc = ram addr 96 | getbyte: 97 | call lptrecv_blocking 98 | ld (bc), a 99 | inc bc ; addr++ 100 | dec hl ; bytes-- 101 | xor a 102 | or h 103 | jr nz, getbyte ; if h != 0, keep reading 104 | xor a 105 | or l 106 | jr nz, getbyte ; if l != 0, keep going 107 | jp 0x8000 ; else, jump to new code in ram 108 | 109 | 110 | ; at idle, lower all control lines 111 | ; 112 | ; writer: reader: 113 | ; raise strobe 114 | ; see high strobe as high busy 115 | ; raise linefeed 116 | ; see high linefeed as high ack 117 | ; write all data pins 118 | ; lower strobe 119 | ; see low strobe as low busy 120 | ; read data 121 | ; lower linefeed 122 | ; see lower linefeed as high ack 123 | 124 | ; return byte in a 125 | lptrecv_blocking: 126 | push hl 127 | wait_for_busy: 128 | in a, (#STATUS_PORT) 129 | and #STATUS_BUSY ; is busy high? (strobe on writer) 130 | jr z, wait_for_busy ; no, wait until it is 131 | and #STATUS_ACK ; but is ack high too? that's bogus 132 | jr nz, wait_for_busy 133 | ld a, #CONTROL_LINEFEED ; raise linefeed 134 | out (#CONTROL_PORT), a 135 | wait_for_busy_ack: 136 | in a, (#STATUS_PORT) 137 | and #STATUS_BUSY ; is busy high? 138 | jr nz, wait_for_busy_ack ; no, wait 139 | read_data: 140 | in a, (#DATA_PORT) 141 | ld l, a 142 | lower_lf: 143 | xor a 144 | out (#CONTROL_PORT), a ; lower linefeed 145 | ld a, l ; return read byte in a 146 | pop hl 147 | ret 148 | -------------------------------------------------------------------------------- /pcb/gerber/copper_bottom.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INBottom Copper*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | %ADD10C,1.500000*% 12 | %ADD11R,1.500000X1.500000*% 13 | %ADD12P,1.649562X8X22.500000*% 14 | %ADD13C,5.080000*% 15 | %ADD14C,1.320800*% 16 | %ADD15C,1.778000*% 17 | %ADD16C,4.191000*% 18 | %ADD17C,0.152400*% 19 | %ADD18C,0.254000*% 20 | 21 | 22 | D10* 23 | X521970Y566420D03* 24 | X496570Y566420D03* 25 | X471170Y566420D03* 26 | X445770Y566420D03* 27 | X420370Y566420D03* 28 | X394970Y566420D03* 29 | X369570Y566420D03* 30 | X344170Y566420D03* 31 | X318770Y566420D03* 32 | X293370Y566420D03* 33 | X267970Y566420D03* 34 | X242570Y566420D03* 35 | X217170Y566420D03* 36 | X191770Y566420D03* 37 | X521970Y490220D03* 38 | X496570Y490220D03* 39 | X471170Y490220D03* 40 | X445770Y490220D03* 41 | X420370Y490220D03* 42 | X394970Y490220D03* 43 | X369570Y490220D03* 44 | X344170Y490220D03* 45 | X318770Y490220D03* 46 | X293370Y490220D03* 47 | X267970Y490220D03* 48 | X242570Y490220D03* 49 | X217170Y490220D03* 50 | D11* 51 | X191770Y490220D03* 52 | D12* 53 | X189992Y711200D03* 54 | X217678Y711200D03* 55 | X245110Y711200D03* 56 | X272796Y711200D03* 57 | X300482Y711200D03* 58 | X327914Y711200D03* 59 | X355600Y711200D03* 60 | X383286Y711200D03* 61 | X410718Y711200D03* 62 | X438404Y711200D03* 63 | X466090Y711200D03* 64 | X493522Y711200D03* 65 | X521208Y711200D03* 66 | X203708Y736600D03* 67 | X231394Y736600D03* 68 | X259080Y736600D03* 69 | X286512Y736600D03* 70 | X314198Y736600D03* 71 | X341884Y736600D03* 72 | X369316Y736600D03* 73 | X397002Y736600D03* 74 | X424688Y736600D03* 75 | X452120Y736600D03* 76 | X479806Y736600D03* 77 | X507492Y736600D03* 78 | D13* 79 | X120396Y723900D03* 80 | X590804Y723900D03* 81 | D14* 82 | X226060Y677164D02* 83 | X226060Y663956D01* 84 | X251460Y663956D02* 85 | X251460Y677164D01* 86 | X276860Y677164D02* 87 | X276860Y663956D01* 88 | X302260Y663956D02* 89 | X302260Y677164D01* 90 | X327660Y677164D02* 91 | X327660Y663956D01* 92 | X353060Y663956D02* 93 | X353060Y677164D01* 94 | X378460Y677164D02* 95 | X378460Y663956D01* 96 | X403860Y663956D02* 97 | X403860Y677164D01* 98 | X429260Y677164D02* 99 | X429260Y663956D01* 100 | X454660Y663956D02* 101 | X454660Y677164D01* 102 | X480060Y677164D02* 103 | X480060Y663956D01* 104 | D15* 105 | X246380Y627380D03* 106 | X271780Y627380D03* 107 | X297180Y627380D03* 108 | X322580Y627380D03* 109 | X347980Y627380D03* 110 | X373380Y627380D03* 111 | X398780Y627380D03* 112 | X424180Y627380D03* 113 | X449580Y627380D03* 114 | X474980Y627380D03* 115 | X500380Y627380D03* 116 | X525780Y627380D03* 117 | X246380Y424180D03* 118 | X271780Y424180D03* 119 | X297180Y424180D03* 120 | X322580Y424180D03* 121 | X347980Y424180D03* 122 | X373380Y424180D03* 123 | X398780Y424180D03* 124 | X424180Y424180D03* 125 | X449580Y424180D03* 126 | X474980Y424180D03* 127 | X500380Y424180D03* 128 | X525780Y424180D03* 129 | X220980Y424180D03* 130 | X195580Y424180D03* 131 | X170180Y424180D03* 132 | X144780Y424180D03* 133 | D16* 134 | X544830Y669290D03* 135 | X165100Y669290D03* 136 | D17* 137 | X449580Y627380D02* 138 | X449580Y626110D01* 139 | X521970Y490220D02* 140 | X474980Y443230D01* 141 | X443230Y443230D01* 142 | X424180Y424180D01* 143 | X399415Y426720D02* 144 | X398780Y424180D01* 145 | X462915Y456565D02* 146 | X496570Y490220D01* 147 | X462915Y456565D02* 148 | X431165Y456565D01* 149 | X398780Y424180D01* 150 | D18* 151 | X171450Y615950D02* 152 | X226060Y670560D01* 153 | X171450Y459105D02* 154 | X171450Y425450D01* 155 | X171450Y423545D02* 156 | X171450Y615950D01* 157 | D17* 158 | X171450Y425450D02* 159 | X170180Y424180D01* 160 | D18* 161 | X414655Y459105D02* 162 | X445770Y490220D01* 163 | X414655Y459105D02* 164 | X171450Y459105D01* 165 | D17* 166 | X251460Y670560D02* 167 | X252730Y671830D01* 168 | X250825Y673100D02* 169 | X250825Y711200D01* 170 | X245110Y711200D01* 171 | X250825Y673100D02* 172 | X251460Y670560D01* 173 | X242570Y647700D02* 174 | X279400Y647700D01* 175 | X242570Y647700D02* 176 | X217170Y622300D01* 177 | X217170Y566420D01* 178 | X279400Y647700D02* 179 | X302260Y670560D01* 180 | X284480Y614680D02* 181 | X242570Y572770D01* 182 | X284480Y614680D02* 183 | X284480Y637540D01* 184 | X242570Y572770D02* 185 | X242570Y566420D01* 186 | X284480Y637540D02* 187 | X294640Y647700D01* 188 | X307340Y647700D01* 189 | X327660Y668020D01* 190 | X327660Y670560D01* 191 | X309880Y614680D02* 192 | X267970Y572770D01* 193 | X309880Y614680D02* 194 | X309880Y638810D01* 195 | X267970Y572770D02* 196 | X267970Y566420D01* 197 | X309880Y638810D02* 198 | X318770Y647700D01* 199 | X332740Y647700D01* 200 | X353060Y668020D01* 201 | X353060Y670560D01* 202 | X410718Y711200D02* 203 | X412750Y711200D01* 204 | X406400Y711200D02* 205 | X406400Y673100D01* 206 | X403860Y670560D02* 207 | X406400Y668020D01* 208 | X386080Y647700D01* 209 | X386080Y615315D01* 210 | X406400Y711200D02* 211 | X410718Y711200D01* 212 | X406400Y673100D02* 213 | X403860Y670560D01* 214 | X363220Y610870D02* 215 | X318770Y566420D01* 216 | X363220Y610870D02* 217 | X381635Y610870D01* 218 | X386080Y615315D01* 219 | X431800Y673100D02* 220 | X431800Y711200D01* 221 | X438404Y711200D01* 222 | X431800Y673100D02* 223 | X429260Y670560D01* 224 | X384810Y605155D02* 225 | X346075Y566420D01* 226 | X344170Y566420D01* 227 | X384810Y605155D02* 228 | X399415Y605155D01* 229 | X429260Y665480D02* 230 | X429260Y670560D01* 231 | X429260Y665480D02* 232 | X411480Y647700D01* 233 | X411480Y617220D01* 234 | X399415Y605155D01* 235 | X454660Y670560D02* 236 | X454660Y693420D01* 237 | X466090Y704850D02* 238 | X466090Y711200D01* 239 | X466090Y704850D02* 240 | X454660Y693420D01* 241 | X395605Y595630D02* 242 | X369570Y569595D01* 243 | X395605Y595630D02* 244 | X413385Y595630D01* 245 | X436880Y619125D01* 246 | X436880Y647700D01* 247 | X369570Y569595D02* 248 | X369570Y566420D01* 249 | X454660Y665480D02* 250 | X454660Y670560D01* 251 | X454660Y665480D02* 252 | X436880Y647700D01* 253 | X481330Y671830D02* 254 | X481330Y695198D01* 255 | X481330Y671830D02* 256 | X480060Y670560D01* 257 | X493522Y707390D02* 258 | X493522Y711200D01* 259 | X493522Y707390D02* 260 | X481330Y695198D01* 261 | X394970Y569595D02* 262 | X394970Y566420D01* 263 | X394970Y569595D02* 264 | X410845Y585470D01* 265 | X428625Y585470D01* 266 | X462280Y619125D01* 267 | X462280Y647700D01* 268 | X480060Y665480D02* 269 | X480060Y670560D01* 270 | X480060Y665480D02* 271 | X462280Y647700D01* 272 | X242570Y493395D02* 273 | X242570Y490220D01* 274 | X242570Y493395D02* 275 | X294640Y545465D01* 276 | X474345Y545465D01* 277 | X483870Y554990D01* 278 | X487680Y647700D02* 279 | X502920Y662940D01* 280 | X502920Y685800D01* 281 | X521208Y704088D02* 282 | X521208Y711200D01* 283 | X521208Y704088D02* 284 | X502920Y685800D01* 285 | X487680Y614680D02* 286 | X483870Y610870D01* 287 | X483870Y554990D01* 288 | X487680Y614680D02* 289 | X487680Y647700D01* 290 | X508000Y736092D02* 291 | X507492Y736600D01* 292 | X267970Y493395D02* 293 | X267970Y490220D01* 294 | X267970Y493395D02* 295 | X311785Y537210D01* 296 | X502920Y537210D01* 297 | X509270Y543560D01* 298 | X507492Y736600D02* 299 | X527812Y736600D01* 300 | X513080Y614680D02* 301 | X509270Y610870D01* 302 | X509270Y543560D01* 303 | X535940Y728472D02* 304 | X527812Y736600D01* 305 | X535940Y728472D02* 306 | X535940Y702310D01* 307 | X513080Y679450D02* 308 | X513080Y614680D01* 309 | X513080Y679450D02* 310 | X535940Y702310D01* 311 | M02* 312 | -------------------------------------------------------------------------------- /pcb/gerber/copper_top.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INTop Copper*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | %ADD10C,1.500000*% 12 | %ADD11R,1.500000X1.500000*% 13 | %ADD12P,1.649562X8X22.500000*% 14 | %ADD13C,5.080000*% 15 | %ADD14C,1.320800*% 16 | %ADD15C,1.778000*% 17 | %ADD16C,4.191000*% 18 | %ADD17C,0.254000*% 19 | %ADD18C,0.152400*% 20 | 21 | 22 | D10* 23 | X521970Y566420D03* 24 | X496570Y566420D03* 25 | X471170Y566420D03* 26 | X445770Y566420D03* 27 | X420370Y566420D03* 28 | X394970Y566420D03* 29 | X369570Y566420D03* 30 | X344170Y566420D03* 31 | X318770Y566420D03* 32 | X293370Y566420D03* 33 | X267970Y566420D03* 34 | X242570Y566420D03* 35 | X217170Y566420D03* 36 | X191770Y566420D03* 37 | X521970Y490220D03* 38 | X496570Y490220D03* 39 | X471170Y490220D03* 40 | X445770Y490220D03* 41 | X420370Y490220D03* 42 | X394970Y490220D03* 43 | X369570Y490220D03* 44 | X344170Y490220D03* 45 | X318770Y490220D03* 46 | X293370Y490220D03* 47 | X267970Y490220D03* 48 | X242570Y490220D03* 49 | X217170Y490220D03* 50 | D11* 51 | X191770Y490220D03* 52 | D12* 53 | X189992Y711200D03* 54 | X217678Y711200D03* 55 | X245110Y711200D03* 56 | X272796Y711200D03* 57 | X300482Y711200D03* 58 | X327914Y711200D03* 59 | X355600Y711200D03* 60 | X383286Y711200D03* 61 | X410718Y711200D03* 62 | X438404Y711200D03* 63 | X466090Y711200D03* 64 | X493522Y711200D03* 65 | X521208Y711200D03* 66 | X203708Y736600D03* 67 | X231394Y736600D03* 68 | X259080Y736600D03* 69 | X286512Y736600D03* 70 | X314198Y736600D03* 71 | X341884Y736600D03* 72 | X369316Y736600D03* 73 | X397002Y736600D03* 74 | X424688Y736600D03* 75 | X452120Y736600D03* 76 | X479806Y736600D03* 77 | X507492Y736600D03* 78 | D13* 79 | X120396Y723900D03* 80 | X590804Y723900D03* 81 | D14* 82 | X226060Y677164D02* 83 | X226060Y663956D01* 84 | X251460Y663956D02* 85 | X251460Y677164D01* 86 | X276860Y677164D02* 87 | X276860Y663956D01* 88 | X302260Y663956D02* 89 | X302260Y677164D01* 90 | X327660Y677164D02* 91 | X327660Y663956D01* 92 | X353060Y663956D02* 93 | X353060Y677164D01* 94 | X378460Y677164D02* 95 | X378460Y663956D01* 96 | X403860Y663956D02* 97 | X403860Y677164D01* 98 | X429260Y677164D02* 99 | X429260Y663956D01* 100 | X454660Y663956D02* 101 | X454660Y677164D01* 102 | X480060Y677164D02* 103 | X480060Y663956D01* 104 | D15* 105 | X246380Y627380D03* 106 | X271780Y627380D03* 107 | X297180Y627380D03* 108 | X322580Y627380D03* 109 | X347980Y627380D03* 110 | X373380Y627380D03* 111 | X398780Y627380D03* 112 | X424180Y627380D03* 113 | X449580Y627380D03* 114 | X474980Y627380D03* 115 | X500380Y627380D03* 116 | X525780Y627380D03* 117 | X246380Y424180D03* 118 | X271780Y424180D03* 119 | X297180Y424180D03* 120 | X322580Y424180D03* 121 | X347980Y424180D03* 122 | X373380Y424180D03* 123 | X398780Y424180D03* 124 | X424180Y424180D03* 125 | X449580Y424180D03* 126 | X474980Y424180D03* 127 | X500380Y424180D03* 128 | X525780Y424180D03* 129 | X220980Y424180D03* 130 | X195580Y424180D03* 131 | X170180Y424180D03* 132 | X144780Y424180D03* 133 | D16* 134 | X544830Y669290D03* 135 | X165100Y669290D03* 136 | D17* 137 | X191770Y490220D02* 138 | X191770Y452755D01* 139 | X219075Y425450D01* 140 | D18* 141 | X220980Y424180D01* 142 | D17* 143 | X341884Y736600D02* 144 | X369316Y736600D01* 145 | X397002Y736600D01* 146 | X231394Y736600D02* 147 | X203708Y736600D01* 148 | X259080Y736600D02* 149 | X286512Y736600D01* 150 | X314198Y736600D01* 151 | X259080Y736600D02* 152 | X231394Y736600D01* 153 | X314198Y736600D02* 154 | X341884Y736600D01* 155 | X191770Y543560D02* 156 | X191770Y490220D01* 157 | X191770Y543560D02* 158 | X204470Y556260D01* 159 | X203200Y723900D02* 160 | X203200Y736600D01* 161 | X204470Y723900D02* 162 | X204470Y556260D01* 163 | D18* 164 | X203708Y736600D02* 165 | X203200Y736600D01* 166 | D17* 167 | X203200Y723900D02* 168 | X204470Y723900D01* 169 | X203200Y723900D02* 170 | X120396Y723900D01* 171 | D18* 172 | X496570Y579755D02* 173 | X496570Y566420D01* 174 | X496570Y579755D02* 175 | X450850Y625475D01* 176 | X449580Y627380D01* 177 | X521335Y566420D02* 178 | X521970Y566420D01* 179 | X521335Y566420D02* 180 | X458470Y503555D01* 181 | X458470Y433070D01* 182 | X449580Y424180D01* 183 | X471170Y490220D02* 184 | X553720Y572770D01* 185 | X553720Y599440D01* 186 | X525780Y627380D01* 187 | X318770Y495300D02* 188 | X318770Y490220D01* 189 | X318770Y495300D02* 190 | X280670Y533400D01* 191 | X280670Y593090D01* 192 | X259080Y614680D01* 193 | X259080Y649605D01* 194 | X251460Y657225D02* 195 | X251460Y670560D01* 196 | X251460Y657225D02* 197 | X259080Y649605D01* 198 | X276225Y673100D02* 199 | X276225Y708025D01* 200 | X273050Y711200D01* 201 | X276225Y673100D02* 202 | X276860Y670560D01* 203 | X273050Y711200D02* 204 | X272796Y711200D01* 205 | X293370Y495300D02* 206 | X293370Y490220D01* 207 | X293370Y495300D02* 208 | X255270Y533400D01* 209 | X255270Y596900D01* 210 | X264795Y711200D02* 211 | X272796Y711200D01* 212 | X238760Y645160D02* 213 | X233680Y640080D01* 214 | X238760Y685165D02* 215 | X264795Y711200D01* 216 | X238760Y685165D02* 217 | X238760Y645160D01* 218 | X233680Y640080D02* 219 | X233680Y618490D01* 220 | X255270Y596900D01* 221 | X301625Y673100D02* 222 | X301625Y711200D01* 223 | X301625Y673100D02* 224 | X302260Y670560D01* 225 | X301625Y711200D02* 226 | X300482Y711200D01* 227 | X330200Y711200D02* 228 | X330200Y673100D01* 229 | X327660Y670560D01* 230 | X327914Y711200D02* 231 | X330200Y711200D01* 232 | X355600Y711200D02* 233 | X355600Y673100D01* 234 | X353060Y670560D01* 235 | X381000Y673100D02* 236 | X381000Y711200D01* 237 | X381000Y673100D02* 238 | X378460Y670560D01* 239 | X381000Y711200D02* 240 | X383286Y711200D01* 241 | X358140Y647700D02* 242 | X342900Y647700D01* 243 | X358140Y647700D02* 244 | X378460Y668020D01* 245 | X378460Y670560D01* 246 | X342900Y647700D02* 247 | X335280Y640080D01* 248 | X335280Y613410D02* 249 | X293370Y571500D01* 250 | X293370Y566420D01* 251 | X335280Y613410D02* 252 | X335280Y640080D01* 253 | X394949Y567669D02* 254 | X394970Y566420D01* 255 | M02* 256 | -------------------------------------------------------------------------------- /pcb/gerber/gerber_job.gbrjob: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "Comment": "All values are metric (mm)", 4 | "CreationDate": "2021-04-17T01:54:38Z", 5 | "GenerationSoftware": { 6 | "Application": "EAGLE", 7 | "Vendor": "Autodesk", 8 | "Version": "9.6.2" 9 | }, 10 | "Part": "Single" 11 | }, 12 | "Overall": { 13 | "BoardThickness": 1.57, 14 | "LayerNumber": 2, 15 | "Name": { 16 | "ProjectId": "wifistation" 17 | }, 18 | "Owner": "jcs jcs ", 19 | "Size": { 20 | "X": 54.788, 21 | "Y": 40.884 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pcb/gerber/profile.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %IN*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | %ADD10C,0.254000*% 12 | 13 | 14 | D10* 15 | X81280Y401320D02* 16 | X629160Y401320D01* 17 | X629160Y810160D01* 18 | X81280Y810160D01* 19 | X81280Y401320D01* 20 | M02* 21 | -------------------------------------------------------------------------------- /pcb/gerber/silkscreen_bottom.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INSilkscreen Bottom*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | 12 | 13 | M02* 14 | -------------------------------------------------------------------------------- /pcb/gerber/silkscreen_top.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INSilkscreen Top*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | %ADD10C,0.101600*% 12 | %ADD11C,0.200000*% 13 | %ADD12C,0.150000*% 14 | %ADD13C,0.600000*% 15 | %ADD14C,0.250000*% 16 | %ADD15C,0.152400*% 17 | %ADD16C,0.127000*% 18 | %ADD17C,0.000000*% 19 | 20 | 21 | D10* 22 | X614172Y504952D02* 23 | X602488Y502356D01* 24 | X610277Y499759D01* 25 | X602488Y497163D01* 26 | X614172Y494566D01* 27 | X610277Y490234D02* 28 | X602488Y490234D01* 29 | X613523Y490559D02* 30 | X614172Y490559D01* 31 | X614172Y489910D01* 32 | X613523Y489910D01* 33 | X613523Y490559D01* 34 | X614172Y485075D02* 35 | X602488Y485075D01* 36 | X614172Y485075D02* 37 | X614172Y479882D01* 38 | X608979Y479882D02* 39 | X608979Y485075D01* 40 | X610277Y475756D02* 41 | X602488Y475756D01* 42 | X613523Y476081D02* 43 | X614172Y476081D01* 44 | X614172Y475432D01* 45 | X613523Y475432D01* 46 | X613523Y476081D01* 47 | X602488Y467487D02* 48 | X602490Y467388D01* 49 | X602496Y467288D01* 50 | X602505Y467189D01* 51 | X602518Y467091D01* 52 | X602535Y466993D01* 53 | X602556Y466895D01* 54 | X602581Y466799D01* 55 | X602609Y466704D01* 56 | X602641Y466610D01* 57 | X602676Y466517D01* 58 | X602715Y466425D01* 59 | X602758Y466335D01* 60 | X602803Y466247D01* 61 | X602853Y466160D01* 62 | X602905Y466076D01* 63 | X602961Y465993D01* 64 | X603019Y465913D01* 65 | X603081Y465835D01* 66 | X603146Y465760D01* 67 | X603214Y465687D01* 68 | X603284Y465617D01* 69 | X603357Y465549D01* 70 | X603432Y465484D01* 71 | X603510Y465422D01* 72 | X603590Y465364D01* 73 | X603673Y465308D01* 74 | X603757Y465256D01* 75 | X603844Y465206D01* 76 | X603932Y465161D01* 77 | X604022Y465118D01* 78 | X604114Y465079D01* 79 | X604207Y465044D01* 80 | X604301Y465012D01* 81 | X604396Y464984D01* 82 | X604492Y464959D01* 83 | X604590Y464938D01* 84 | X604688Y464921D01* 85 | X604786Y464908D01* 86 | X604885Y464899D01* 87 | X604985Y464893D01* 88 | X605084Y464891D01* 89 | X602488Y467487D02* 90 | X602490Y467631D01* 91 | X602496Y467776D01* 92 | X602505Y467920D01* 93 | X602518Y468063D01* 94 | X602535Y468207D01* 95 | X602556Y468350D01* 96 | X602581Y468492D01* 97 | X602609Y468633D01* 98 | X602641Y468774D01* 99 | X602677Y468914D01* 100 | X602716Y469053D01* 101 | X602759Y469191D01* 102 | X602806Y469327D01* 103 | X602856Y469463D01* 104 | X602910Y469597D01* 105 | X602967Y469729D01* 106 | X603028Y469860D01* 107 | X603092Y469989D01* 108 | X603160Y470117D01* 109 | X603231Y470243D01* 110 | X603305Y470367D01* 111 | X603382Y470488D01* 112 | X603463Y470608D01* 113 | X603546Y470726D01* 114 | X603633Y470841D01* 115 | X603723Y470954D01* 116 | X603816Y471065D01* 117 | X603911Y471173D01* 118 | X604010Y471279D01* 119 | X604111Y471382D01* 120 | X611576Y471057D02* 121 | X611675Y471055D01* 122 | X611775Y471049D01* 123 | X611874Y471040D01* 124 | X611972Y471027D01* 125 | X612070Y471010D01* 126 | X612168Y470989D01* 127 | X612264Y470964D01* 128 | X612359Y470936D01* 129 | X612453Y470904D01* 130 | X612546Y470869D01* 131 | X612638Y470830D01* 132 | X612728Y470787D01* 133 | X612816Y470742D01* 134 | X612903Y470692D01* 135 | X612987Y470640D01* 136 | X613070Y470584D01* 137 | X613150Y470526D01* 138 | X613228Y470464D01* 139 | X613303Y470399D01* 140 | X613376Y470331D01* 141 | X613446Y470261D01* 142 | X613514Y470188D01* 143 | X613579Y470113D01* 144 | X613641Y470035D01* 145 | X613699Y469955D01* 146 | X613755Y469872D01* 147 | X613807Y469788D01* 148 | X613857Y469701D01* 149 | X613902Y469613D01* 150 | X613945Y469523D01* 151 | X613984Y469431D01* 152 | X614019Y469338D01* 153 | X614051Y469244D01* 154 | X614079Y469149D01* 155 | X614104Y469053D01* 156 | X614125Y468955D01* 157 | X614142Y468857D01* 158 | X614155Y468759D01* 159 | X614164Y468660D01* 160 | X614170Y468560D01* 161 | X614172Y468461D01* 162 | X614170Y468325D01* 163 | X614164Y468189D01* 164 | X614155Y468053D01* 165 | X614142Y467917D01* 166 | X614124Y467782D01* 167 | X614104Y467648D01* 168 | X614079Y467514D01* 169 | X614051Y467380D01* 170 | X614018Y467248D01* 171 | X613983Y467117D01* 172 | X613943Y466986D01* 173 | X613900Y466857D01* 174 | X613854Y466729D01* 175 | X613803Y466603D01* 176 | X613750Y466477D01* 177 | X613692Y466354D01* 178 | X613632Y466232D01* 179 | X613568Y466112D01* 180 | X613500Y465993D01* 181 | X613430Y465877D01* 182 | X613356Y465763D01* 183 | X613279Y465650D01* 184 | X613198Y465540D01* 185 | X609304Y469759D02* 186 | X609357Y469845D01* 187 | X609414Y469929D01* 188 | X609473Y470011D01* 189 | X609536Y470091D01* 190 | X609602Y470168D01* 191 | X609670Y470243D01* 192 | X609742Y470315D01* 193 | X609816Y470384D01* 194 | X609893Y470450D01* 195 | X609972Y470513D01* 196 | X610054Y470573D01* 197 | X610138Y470630D01* 198 | X610224Y470684D01* 199 | X610312Y470734D01* 200 | X610402Y470781D01* 201 | X610493Y470825D01* 202 | X610587Y470864D01* 203 | X610681Y470901D01* 204 | X610777Y470933D01* 205 | X610875Y470962D01* 206 | X610973Y470987D01* 207 | X611072Y471008D01* 208 | X611172Y471026D01* 209 | X611272Y471039D01* 210 | X611373Y471049D01* 211 | X611475Y471055D01* 212 | X611576Y471057D01* 213 | X607356Y466189D02* 214 | X607303Y466103D01* 215 | X607246Y466019D01* 216 | X607187Y465937D01* 217 | X607124Y465857D01* 218 | X607058Y465780D01* 219 | X606990Y465705D01* 220 | X606918Y465633D01* 221 | X606844Y465564D01* 222 | X606767Y465498D01* 223 | X606688Y465435D01* 224 | X606606Y465375D01* 225 | X606522Y465318D01* 226 | X606436Y465264D01* 227 | X606348Y465214D01* 228 | X606258Y465167D01* 229 | X606167Y465123D01* 230 | X606073Y465084D01* 231 | X605979Y465047D01* 232 | X605883Y465015D01* 233 | X605785Y464986D01* 234 | X605687Y464961D01* 235 | X605588Y464940D01* 236 | X605488Y464922D01* 237 | X605388Y464909D01* 238 | X605287Y464899D01* 239 | X605185Y464893D01* 240 | X605084Y464891D01* 241 | X607356Y466189D02* 242 | X609304Y469759D01* 243 | X610277Y461413D02* 244 | X610277Y457518D01* 245 | X614172Y460115D02* 246 | X604435Y460115D01* 247 | X604435Y460114D02* 248 | X604348Y460112D01* 249 | X604260Y460106D01* 250 | X604174Y460096D01* 251 | X604087Y460083D01* 252 | X604002Y460065D01* 253 | X603917Y460044D01* 254 | X603833Y460019D01* 255 | X603751Y459990D01* 256 | X603670Y459957D01* 257 | X603590Y459921D01* 258 | X603512Y459882D01* 259 | X603436Y459838D01* 260 | X603362Y459792D01* 261 | X603291Y459742D01* 262 | X603221Y459689D01* 263 | X603154Y459633D01* 264 | X603090Y459574D01* 265 | X603028Y459512D01* 266 | X602969Y459448D01* 267 | X602913Y459381D01* 268 | X602860Y459311D01* 269 | X602810Y459240D01* 270 | X602764Y459166D01* 271 | X602720Y459090D01* 272 | X602681Y459012D01* 273 | X602645Y458932D01* 274 | X602612Y458851D01* 275 | X602583Y458769D01* 276 | X602558Y458685D01* 277 | X602537Y458600D01* 278 | X602519Y458515D01* 279 | X602506Y458428D01* 280 | X602496Y458342D01* 281 | X602490Y458254D01* 282 | X602488Y458167D01* 283 | X602488Y457518D01* 284 | X607032Y450994D02* 285 | X607032Y448073D01* 286 | X607032Y450994D02* 287 | X607030Y451088D01* 288 | X607024Y451182D01* 289 | X607015Y451275D01* 290 | X607001Y451368D01* 291 | X606984Y451460D01* 292 | X606962Y451552D01* 293 | X606938Y451642D01* 294 | X606909Y451732D01* 295 | X606877Y451820D01* 296 | X606841Y451907D01* 297 | X606801Y451992D01* 298 | X606758Y452075D01* 299 | X606712Y452157D01* 300 | X606662Y452237D01* 301 | X606609Y452314D01* 302 | X606553Y452389D01* 303 | X606494Y452462D01* 304 | X606432Y452533D01* 305 | X606367Y452601D01* 306 | X606299Y452666D01* 307 | X606228Y452728D01* 308 | X606155Y452787D01* 309 | X606080Y452843D01* 310 | X606003Y452896D01* 311 | X605923Y452946D01* 312 | X605841Y452992D01* 313 | X605758Y453035D01* 314 | X605673Y453075D01* 315 | X605586Y453111D01* 316 | X605498Y453143D01* 317 | X605408Y453172D01* 318 | X605318Y453196D01* 319 | X605226Y453218D01* 320 | X605134Y453235D01* 321 | X605041Y453249D01* 322 | X604948Y453258D01* 323 | X604854Y453264D01* 324 | X604760Y453266D01* 325 | X604666Y453264D01* 326 | X604572Y453258D01* 327 | X604479Y453249D01* 328 | X604386Y453235D01* 329 | X604294Y453218D01* 330 | X604202Y453196D01* 331 | X604112Y453172D01* 332 | X604022Y453143D01* 333 | X603934Y453111D01* 334 | X603847Y453075D01* 335 | X603762Y453035D01* 336 | X603679Y452992D01* 337 | X603597Y452946D01* 338 | X603517Y452896D01* 339 | X603440Y452843D01* 340 | X603365Y452787D01* 341 | X603292Y452728D01* 342 | X603221Y452666D01* 343 | X603153Y452601D01* 344 | X603088Y452533D01* 345 | X603026Y452462D01* 346 | X602967Y452389D01* 347 | X602911Y452314D01* 348 | X602858Y452237D01* 349 | X602808Y452157D01* 350 | X602762Y452075D01* 351 | X602719Y451992D01* 352 | X602679Y451907D01* 353 | X602643Y451820D01* 354 | X602611Y451732D01* 355 | X602582Y451642D01* 356 | X602558Y451552D01* 357 | X602536Y451460D01* 358 | X602519Y451368D01* 359 | X602505Y451275D01* 360 | X602496Y451182D01* 361 | X602490Y451088D01* 362 | X602488Y450994D01* 363 | X602488Y448073D01* 364 | X608330Y448073D01* 365 | X608417Y448075D01* 366 | X608505Y448081D01* 367 | X608591Y448091D01* 368 | X608678Y448104D01* 369 | X608763Y448122D01* 370 | X608848Y448143D01* 371 | X608932Y448168D01* 372 | X609014Y448197D01* 373 | X609095Y448230D01* 374 | X609175Y448266D01* 375 | X609253Y448305D01* 376 | X609329Y448349D01* 377 | X609403Y448395D01* 378 | X609474Y448445D01* 379 | X609544Y448498D01* 380 | X609611Y448554D01* 381 | X609676Y448613D01* 382 | X609737Y448675D01* 383 | X609796Y448739D01* 384 | X609852Y448806D01* 385 | X609905Y448876D01* 386 | X609955Y448947D01* 387 | X610001Y449021D01* 388 | X610045Y449097D01* 389 | X610084Y449175D01* 390 | X610120Y449255D01* 391 | X610153Y449336D01* 392 | X610182Y449418D01* 393 | X610207Y449502D01* 394 | X610228Y449587D01* 395 | X610246Y449672D01* 396 | X610259Y449759D01* 397 | X610269Y449846D01* 398 | X610275Y449933D01* 399 | X610277Y450020D01* 400 | X610277Y452617D01* 401 | X610277Y443887D02* 402 | X610277Y439992D01* 403 | X614172Y442589D02* 404 | X604435Y442589D01* 405 | X604435Y442588D02* 406 | X604348Y442586D01* 407 | X604260Y442580D01* 408 | X604174Y442570D01* 409 | X604087Y442557D01* 410 | X604002Y442539D01* 411 | X603917Y442518D01* 412 | X603833Y442493D01* 413 | X603751Y442464D01* 414 | X603670Y442431D01* 415 | X603590Y442395D01* 416 | X603512Y442356D01* 417 | X603436Y442312D01* 418 | X603362Y442266D01* 419 | X603291Y442216D01* 420 | X603221Y442163D01* 421 | X603154Y442107D01* 422 | X603090Y442048D01* 423 | X603028Y441986D01* 424 | X602969Y441922D01* 425 | X602913Y441855D01* 426 | X602860Y441785D01* 427 | X602810Y441714D01* 428 | X602764Y441640D01* 429 | X602720Y441564D01* 430 | X602681Y441486D01* 431 | X602645Y441406D01* 432 | X602612Y441325D01* 433 | X602583Y441243D01* 434 | X602558Y441159D01* 435 | X602537Y441074D01* 436 | X602519Y440989D01* 437 | X602506Y440902D01* 438 | X602496Y440816D01* 439 | X602490Y440728D01* 440 | X602488Y440641D01* 441 | X602488Y439992D01* 442 | X602488Y435751D02* 443 | X610277Y435751D01* 444 | X613523Y436076D02* 445 | X614172Y436076D01* 446 | X614172Y435427D01* 447 | X613523Y435427D01* 448 | X613523Y436076D01* 449 | X607681Y431109D02* 450 | X605084Y431109D01* 451 | X607681Y431108D02* 452 | X607782Y431106D01* 453 | X607882Y431100D01* 454 | X607982Y431090D01* 455 | X608082Y431077D01* 456 | X608181Y431059D01* 457 | X608280Y431038D01* 458 | X608377Y431013D01* 459 | X608474Y430984D01* 460 | X608569Y430951D01* 461 | X608663Y430915D01* 462 | X608755Y430875D01* 463 | X608846Y430832D01* 464 | X608935Y430785D01* 465 | X609022Y430735D01* 466 | X609108Y430681D01* 467 | X609191Y430624D01* 468 | X609271Y430564D01* 469 | X609350Y430501D01* 470 | X609426Y430434D01* 471 | X609499Y430365D01* 472 | X609569Y430293D01* 473 | X609637Y430219D01* 474 | X609702Y430142D01* 475 | X609763Y430062D01* 476 | X609822Y429980D01* 477 | X609877Y429896D01* 478 | X609929Y429810D01* 479 | X609978Y429722D01* 480 | X610023Y429632D01* 481 | X610065Y429540D01* 482 | X610103Y429447D01* 483 | X610137Y429352D01* 484 | X610168Y429257D01* 485 | X610195Y429160D01* 486 | X610218Y429062D01* 487 | X610238Y428963D01* 488 | X610253Y428863D01* 489 | X610265Y428763D01* 490 | X610273Y428663D01* 491 | X610277Y428562D01* 492 | X610277Y428462D01* 493 | X610273Y428361D01* 494 | X610265Y428261D01* 495 | X610253Y428161D01* 496 | X610238Y428061D01* 497 | X610218Y427962D01* 498 | X610195Y427864D01* 499 | X610168Y427767D01* 500 | X610137Y427672D01* 501 | X610103Y427577D01* 502 | X610065Y427484D01* 503 | X610023Y427392D01* 504 | X609978Y427302D01* 505 | X609929Y427214D01* 506 | X609877Y427128D01* 507 | X609822Y427044D01* 508 | X609763Y426962D01* 509 | X609702Y426882D01* 510 | X609637Y426805D01* 511 | X609569Y426731D01* 512 | X609499Y426659D01* 513 | X609426Y426590D01* 514 | X609350Y426523D01* 515 | X609271Y426460D01* 516 | X609191Y426400D01* 517 | X609108Y426343D01* 518 | X609022Y426289D01* 519 | X608935Y426239D01* 520 | X608846Y426192D01* 521 | X608755Y426149D01* 522 | X608663Y426109D01* 523 | X608569Y426073D01* 524 | X608474Y426040D01* 525 | X608377Y426011D01* 526 | X608280Y425986D01* 527 | X608181Y425965D01* 528 | X608082Y425947D01* 529 | X607982Y425934D01* 530 | X607882Y425924D01* 531 | X607782Y425918D01* 532 | X607681Y425916D01* 533 | X605084Y425916D01* 534 | X604983Y425918D01* 535 | X604883Y425924D01* 536 | X604783Y425934D01* 537 | X604683Y425947D01* 538 | X604584Y425965D01* 539 | X604485Y425986D01* 540 | X604388Y426011D01* 541 | X604291Y426040D01* 542 | X604196Y426073D01* 543 | X604102Y426109D01* 544 | X604010Y426149D01* 545 | X603919Y426192D01* 546 | X603830Y426239D01* 547 | X603743Y426289D01* 548 | X603657Y426343D01* 549 | X603574Y426400D01* 550 | X603494Y426460D01* 551 | X603415Y426523D01* 552 | X603339Y426590D01* 553 | X603266Y426659D01* 554 | X603196Y426731D01* 555 | X603128Y426805D01* 556 | X603063Y426882D01* 557 | X603002Y426962D01* 558 | X602943Y427044D01* 559 | X602888Y427128D01* 560 | X602836Y427214D01* 561 | X602787Y427302D01* 562 | X602742Y427392D01* 563 | X602700Y427484D01* 564 | X602662Y427577D01* 565 | X602628Y427672D01* 566 | X602597Y427767D01* 567 | X602570Y427864D01* 568 | X602547Y427962D01* 569 | X602527Y428061D01* 570 | X602512Y428161D01* 571 | X602500Y428261D01* 572 | X602492Y428361D01* 573 | X602488Y428462D01* 574 | X602488Y428562D01* 575 | X602492Y428663D01* 576 | X602500Y428763D01* 577 | X602512Y428863D01* 578 | X602527Y428963D01* 579 | X602547Y429062D01* 580 | X602570Y429160D01* 581 | X602597Y429257D01* 582 | X602628Y429352D01* 583 | X602662Y429447D01* 584 | X602700Y429540D01* 585 | X602742Y429632D01* 586 | X602787Y429722D01* 587 | X602836Y429810D01* 588 | X602888Y429896D01* 589 | X602943Y429980D01* 590 | X603002Y430062D01* 591 | X603063Y430142D01* 592 | X603128Y430219D01* 593 | X603196Y430293D01* 594 | X603266Y430365D01* 595 | X603339Y430434D01* 596 | X603415Y430501D01* 597 | X603494Y430564D01* 598 | X603574Y430624D01* 599 | X603657Y430681D01* 600 | X603743Y430735D01* 601 | X603830Y430785D01* 602 | X603919Y430832D01* 603 | X604010Y430875D01* 604 | X604102Y430915D01* 605 | X604196Y430951D01* 606 | X604291Y430984D01* 607 | X604388Y431013D01* 608 | X604485Y431038D01* 609 | X604584Y431059D01* 610 | X604683Y431077D01* 611 | X604783Y431090D01* 612 | X604883Y431100D01* 613 | X604983Y431106D01* 614 | X605084Y431108D01* 615 | X602488Y420822D02* 616 | X610277Y420822D01* 617 | X610277Y417576D01* 618 | X610275Y417489D01* 619 | X610269Y417401D01* 620 | X610259Y417315D01* 621 | X610246Y417228D01* 622 | X610228Y417143D01* 623 | X610207Y417058D01* 624 | X610182Y416974D01* 625 | X610153Y416892D01* 626 | X610120Y416811D01* 627 | X610084Y416731D01* 628 | X610045Y416653D01* 629 | X610001Y416577D01* 630 | X609955Y416503D01* 631 | X609905Y416432D01* 632 | X609852Y416362D01* 633 | X609796Y416295D01* 634 | X609737Y416231D01* 635 | X609676Y416169D01* 636 | X609611Y416110D01* 637 | X609544Y416054D01* 638 | X609474Y416001D01* 639 | X609403Y415951D01* 640 | X609329Y415905D01* 641 | X609253Y415862D01* 642 | X609175Y415822D01* 643 | X609095Y415786D01* 644 | X609014Y415753D01* 645 | X608932Y415724D01* 646 | X608848Y415699D01* 647 | X608763Y415678D01* 648 | X608678Y415660D01* 649 | X608591Y415647D01* 650 | X608505Y415637D01* 651 | X608417Y415631D01* 652 | X608330Y415629D01* 653 | X602488Y415629D01* 654 | D11* 655 | X175870Y521970D02* 656 | X176028Y521972D01* 657 | X176187Y521978D01* 658 | X176345Y521988D01* 659 | X176502Y522002D01* 660 | X176660Y522019D01* 661 | X176816Y522041D01* 662 | X176973Y522066D01* 663 | X177128Y522096D01* 664 | X177283Y522129D01* 665 | X177437Y522166D01* 666 | X177590Y522207D01* 667 | X177742Y522252D01* 668 | X177892Y522301D01* 669 | X178042Y522353D01* 670 | X178190Y522409D01* 671 | X178337Y522469D01* 672 | X178482Y522532D01* 673 | X178625Y522599D01* 674 | X178767Y522669D01* 675 | X178907Y522743D01* 676 | X179045Y522821D01* 677 | X179181Y522902D01* 678 | X179315Y522986D01* 679 | X179447Y523073D01* 680 | X179577Y523164D01* 681 | X179704Y523258D01* 682 | X179829Y523355D01* 683 | X179952Y523456D01* 684 | X180072Y523559D01* 685 | X180189Y523665D01* 686 | X180304Y523774D01* 687 | X180416Y523886D01* 688 | X180525Y524001D01* 689 | X180631Y524118D01* 690 | X180734Y524238D01* 691 | X180835Y524361D01* 692 | X180932Y524486D01* 693 | X181026Y524613D01* 694 | X181117Y524743D01* 695 | X181204Y524875D01* 696 | X181288Y525009D01* 697 | X181369Y525145D01* 698 | X181447Y525283D01* 699 | X181521Y525423D01* 700 | X181591Y525565D01* 701 | X181658Y525708D01* 702 | X181721Y525853D01* 703 | X181781Y526000D01* 704 | X181837Y526148D01* 705 | X181889Y526298D01* 706 | X181938Y526448D01* 707 | X181983Y526600D01* 708 | X182024Y526753D01* 709 | X182061Y526907D01* 710 | X182094Y527062D01* 711 | X182124Y527217D01* 712 | X182149Y527374D01* 713 | X182171Y527530D01* 714 | X182188Y527688D01* 715 | X182202Y527845D01* 716 | X182212Y528003D01* 717 | X182218Y528162D01* 718 | X182220Y528320D01* 719 | X182218Y528478D01* 720 | X182212Y528637D01* 721 | X182202Y528795D01* 722 | X182188Y528952D01* 723 | X182171Y529110D01* 724 | X182149Y529266D01* 725 | X182124Y529423D01* 726 | X182094Y529578D01* 727 | X182061Y529733D01* 728 | X182024Y529887D01* 729 | X181983Y530040D01* 730 | X181938Y530192D01* 731 | X181889Y530342D01* 732 | X181837Y530492D01* 733 | X181781Y530640D01* 734 | X181721Y530787D01* 735 | X181658Y530932D01* 736 | X181591Y531075D01* 737 | X181521Y531217D01* 738 | X181447Y531357D01* 739 | X181369Y531495D01* 740 | X181288Y531631D01* 741 | X181204Y531765D01* 742 | X181117Y531897D01* 743 | X181026Y532027D01* 744 | X180932Y532154D01* 745 | X180835Y532279D01* 746 | X180734Y532402D01* 747 | X180631Y532522D01* 748 | X180525Y532639D01* 749 | X180416Y532754D01* 750 | X180304Y532866D01* 751 | X180189Y532975D01* 752 | X180072Y533081D01* 753 | X179952Y533184D01* 754 | X179829Y533285D01* 755 | X179704Y533382D01* 756 | X179577Y533476D01* 757 | X179447Y533567D01* 758 | X179315Y533654D01* 759 | X179181Y533738D01* 760 | X179045Y533819D01* 761 | X178907Y533897D01* 762 | X178767Y533971D01* 763 | X178625Y534041D01* 764 | X178482Y534108D01* 765 | X178337Y534171D01* 766 | X178190Y534231D01* 767 | X178042Y534287D01* 768 | X177892Y534339D01* 769 | X177742Y534388D01* 770 | X177590Y534433D01* 771 | X177437Y534474D01* 772 | X177283Y534511D01* 773 | X177128Y534544D01* 774 | X176973Y534574D01* 775 | X176816Y534599D01* 776 | X176660Y534621D01* 777 | X176502Y534638D01* 778 | X176345Y534652D01* 779 | X176187Y534662D01* 780 | X176028Y534668D01* 781 | X175870Y534670D01* 782 | D12* 783 | X537870Y554820D02* 784 | X537870Y501820D01* 785 | X175870Y534670D02* 786 | X175870Y554820D01* 787 | X175870Y521970D02* 788 | X175870Y501820D01* 789 | X175870Y554820D02* 790 | X537870Y554820D01* 791 | X537870Y501820D02* 792 | X175870Y501820D01* 793 | D13* 794 | X182870Y511820D02* 795 | X182872Y511929D01* 796 | X182878Y512038D01* 797 | X182888Y512146D01* 798 | X182902Y512254D01* 799 | X182919Y512362D01* 800 | X182941Y512469D01* 801 | X182966Y512575D01* 802 | X182996Y512679D01* 803 | X183029Y512783D01* 804 | X183066Y512886D01* 805 | X183106Y512987D01* 806 | X183150Y513086D01* 807 | X183198Y513184D01* 808 | X183250Y513281D01* 809 | X183304Y513375D01* 810 | X183362Y513467D01* 811 | X183424Y513557D01* 812 | X183489Y513644D01* 813 | X183556Y513730D01* 814 | X183627Y513813D01* 815 | X183701Y513893D01* 816 | X183778Y513970D01* 817 | X183857Y514045D01* 818 | X183939Y514116D01* 819 | X184024Y514185D01* 820 | X184111Y514250D01* 821 | X184200Y514313D01* 822 | X184292Y514371D01* 823 | X184386Y514427D01* 824 | X184481Y514479D01* 825 | X184579Y514528D01* 826 | X184678Y514573D01* 827 | X184779Y514615D01* 828 | X184881Y514652D01* 829 | X184984Y514686D01* 830 | X185089Y514717D01* 831 | X185195Y514743D01* 832 | X185301Y514766D01* 833 | X185409Y514784D01* 834 | X185517Y514799D01* 835 | X185625Y514810D01* 836 | X185734Y514817D01* 837 | X185843Y514820D01* 838 | X185952Y514819D01* 839 | X186061Y514814D01* 840 | X186169Y514805D01* 841 | X186277Y514792D01* 842 | X186385Y514775D01* 843 | X186492Y514755D01* 844 | X186598Y514730D01* 845 | X186703Y514702D01* 846 | X186807Y514670D01* 847 | X186910Y514634D01* 848 | X187012Y514594D01* 849 | X187112Y514551D01* 850 | X187210Y514504D01* 851 | X187307Y514454D01* 852 | X187401Y514400D01* 853 | X187494Y514342D01* 854 | X187585Y514282D01* 855 | X187673Y514218D01* 856 | X187759Y514151D01* 857 | X187842Y514081D01* 858 | X187923Y514008D01* 859 | X188001Y513932D01* 860 | X188076Y513853D01* 861 | X188149Y513771D01* 862 | X188218Y513687D01* 863 | X188284Y513601D01* 864 | X188347Y513512D01* 865 | X188407Y513421D01* 866 | X188464Y513328D01* 867 | X188517Y513233D01* 868 | X188566Y513136D01* 869 | X188612Y513037D01* 870 | X188654Y512937D01* 871 | X188693Y512835D01* 872 | X188728Y512731D01* 873 | X188759Y512627D01* 874 | X188787Y512522D01* 875 | X188810Y512415D01* 876 | X188830Y512308D01* 877 | X188846Y512200D01* 878 | X188858Y512092D01* 879 | X188866Y511983D01* 880 | X188870Y511874D01* 881 | X188870Y511766D01* 882 | X188866Y511657D01* 883 | X188858Y511548D01* 884 | X188846Y511440D01* 885 | X188830Y511332D01* 886 | X188810Y511225D01* 887 | X188787Y511118D01* 888 | X188759Y511013D01* 889 | X188728Y510909D01* 890 | X188693Y510805D01* 891 | X188654Y510703D01* 892 | X188612Y510603D01* 893 | X188566Y510504D01* 894 | X188517Y510407D01* 895 | X188464Y510312D01* 896 | X188407Y510219D01* 897 | X188347Y510128D01* 898 | X188284Y510039D01* 899 | X188218Y509953D01* 900 | X188149Y509869D01* 901 | X188076Y509787D01* 902 | X188001Y509708D01* 903 | X187923Y509632D01* 904 | X187842Y509559D01* 905 | X187759Y509489D01* 906 | X187673Y509422D01* 907 | X187585Y509358D01* 908 | X187494Y509298D01* 909 | X187401Y509240D01* 910 | X187307Y509186D01* 911 | X187210Y509136D01* 912 | X187112Y509089D01* 913 | X187012Y509046D01* 914 | X186910Y509006D01* 915 | X186807Y508970D01* 916 | X186703Y508938D01* 917 | X186598Y508910D01* 918 | X186492Y508885D01* 919 | X186385Y508865D01* 920 | X186277Y508848D01* 921 | X186169Y508835D01* 922 | X186061Y508826D01* 923 | X185952Y508821D01* 924 | X185843Y508820D01* 925 | X185734Y508823D01* 926 | X185625Y508830D01* 927 | X185517Y508841D01* 928 | X185409Y508856D01* 929 | X185301Y508874D01* 930 | X185195Y508897D01* 931 | X185089Y508923D01* 932 | X184984Y508954D01* 933 | X184881Y508988D01* 934 | X184779Y509025D01* 935 | X184678Y509067D01* 936 | X184579Y509112D01* 937 | X184481Y509161D01* 938 | X184386Y509213D01* 939 | X184292Y509269D01* 940 | X184200Y509327D01* 941 | X184111Y509390D01* 942 | X184024Y509455D01* 943 | X183939Y509524D01* 944 | X183857Y509595D01* 945 | X183778Y509670D01* 946 | X183701Y509747D01* 947 | X183627Y509827D01* 948 | X183556Y509910D01* 949 | X183489Y509996D01* 950 | X183424Y510083D01* 951 | X183362Y510173D01* 952 | X183304Y510265D01* 953 | X183250Y510359D01* 954 | X183198Y510456D01* 955 | X183150Y510554D01* 956 | X183106Y510653D01* 957 | X183066Y510754D01* 958 | X183029Y510857D01* 959 | X182996Y510961D01* 960 | X182966Y511065D01* 961 | X182941Y511171D01* 962 | X182919Y511278D01* 963 | X182902Y511386D01* 964 | X182888Y511494D01* 965 | X182878Y511602D01* 966 | X182872Y511711D01* 967 | X182870Y511820D01* 968 | D14* 969 | X177520Y490220D02* 970 | X177522Y490290D01* 971 | X177528Y490360D01* 972 | X177538Y490429D01* 973 | X177551Y490498D01* 974 | X177569Y490566D01* 975 | X177590Y490633D01* 976 | X177615Y490698D01* 977 | X177644Y490762D01* 978 | X177676Y490825D01* 979 | X177712Y490885D01* 980 | X177751Y490943D01* 981 | X177793Y490999D01* 982 | X177838Y491053D01* 983 | X177886Y491104D01* 984 | X177937Y491152D01* 985 | X177991Y491197D01* 986 | X178047Y491239D01* 987 | X178105Y491278D01* 988 | X178165Y491314D01* 989 | X178228Y491346D01* 990 | X178292Y491375D01* 991 | X178357Y491400D01* 992 | X178424Y491421D01* 993 | X178492Y491439D01* 994 | X178561Y491452D01* 995 | X178630Y491462D01* 996 | X178700Y491468D01* 997 | X178770Y491470D01* 998 | X178840Y491468D01* 999 | X178910Y491462D01* 1000 | X178979Y491452D01* 1001 | X179048Y491439D01* 1002 | X179116Y491421D01* 1003 | X179183Y491400D01* 1004 | X179248Y491375D01* 1005 | X179312Y491346D01* 1006 | X179375Y491314D01* 1007 | X179435Y491278D01* 1008 | X179493Y491239D01* 1009 | X179549Y491197D01* 1010 | X179603Y491152D01* 1011 | X179654Y491104D01* 1012 | X179702Y491053D01* 1013 | X179747Y490999D01* 1014 | X179789Y490943D01* 1015 | X179828Y490885D01* 1016 | X179864Y490825D01* 1017 | X179896Y490762D01* 1018 | X179925Y490698D01* 1019 | X179950Y490633D01* 1020 | X179971Y490566D01* 1021 | X179989Y490498D01* 1022 | X180002Y490429D01* 1023 | X180012Y490360D01* 1024 | X180018Y490290D01* 1025 | X180020Y490220D01* 1026 | X180018Y490150D01* 1027 | X180012Y490080D01* 1028 | X180002Y490011D01* 1029 | X179989Y489942D01* 1030 | X179971Y489874D01* 1031 | X179950Y489807D01* 1032 | X179925Y489742D01* 1033 | X179896Y489678D01* 1034 | X179864Y489615D01* 1035 | X179828Y489555D01* 1036 | X179789Y489497D01* 1037 | X179747Y489441D01* 1038 | X179702Y489387D01* 1039 | X179654Y489336D01* 1040 | X179603Y489288D01* 1041 | X179549Y489243D01* 1042 | X179493Y489201D01* 1043 | X179435Y489162D01* 1044 | X179375Y489126D01* 1045 | X179312Y489094D01* 1046 | X179248Y489065D01* 1047 | X179183Y489040D01* 1048 | X179116Y489019D01* 1049 | X179048Y489001D01* 1050 | X178979Y488988D01* 1051 | X178910Y488978D01* 1052 | X178840Y488972D01* 1053 | X178770Y488970D01* 1054 | X178700Y488972D01* 1055 | X178630Y488978D01* 1056 | X178561Y488988D01* 1057 | X178492Y489001D01* 1058 | X178424Y489019D01* 1059 | X178357Y489040D01* 1060 | X178292Y489065D01* 1061 | X178228Y489094D01* 1062 | X178165Y489126D01* 1063 | X178105Y489162D01* 1064 | X178047Y489201D01* 1065 | X177991Y489243D01* 1066 | X177937Y489288D01* 1067 | X177886Y489336D01* 1068 | X177838Y489387D01* 1069 | X177793Y489441D01* 1070 | X177751Y489497D01* 1071 | X177712Y489555D01* 1072 | X177676Y489615D01* 1073 | X177644Y489678D01* 1074 | X177615Y489742D01* 1075 | X177590Y489807D01* 1076 | X177569Y489874D01* 1077 | X177551Y489942D01* 1078 | X177538Y490011D01* 1079 | X177528Y490080D01* 1080 | X177522Y490150D01* 1081 | X177520Y490220D01* 1082 | D15* 1083 | X103886Y723900D02* 1084 | X103891Y724305D01* 1085 | X103906Y724710D01* 1086 | X103931Y725115D01* 1087 | X103966Y725518D01* 1088 | X104010Y725921D01* 1089 | X104065Y726323D01* 1090 | X104129Y726723D01* 1091 | X104203Y727121D01* 1092 | X104287Y727517D01* 1093 | X104381Y727912D01* 1094 | X104484Y728303D01* 1095 | X104597Y728693D01* 1096 | X104719Y729079D01* 1097 | X104851Y729462D01* 1098 | X104992Y729842D01* 1099 | X105143Y730218D01* 1100 | X105302Y730591D01* 1101 | X105471Y730959D01* 1102 | X105649Y731323D01* 1103 | X105835Y731683D01* 1104 | X106031Y732038D01* 1105 | X106235Y732388D01* 1106 | X106447Y732733D01* 1107 | X106668Y733072D01* 1108 | X106898Y733407D01* 1109 | X107135Y733735D01* 1110 | X107380Y734057D01* 1111 | X107634Y734374D01* 1112 | X107894Y734684D01* 1113 | X108163Y734987D01* 1114 | X108439Y735284D01* 1115 | X108722Y735574D01* 1116 | X109012Y735857D01* 1117 | X109309Y736133D01* 1118 | X109612Y736402D01* 1119 | X109922Y736662D01* 1120 | X110239Y736916D01* 1121 | X110561Y737161D01* 1122 | X110889Y737398D01* 1123 | X111224Y737628D01* 1124 | X111563Y737849D01* 1125 | X111908Y738061D01* 1126 | X112258Y738265D01* 1127 | X112613Y738461D01* 1128 | X112973Y738647D01* 1129 | X113337Y738825D01* 1130 | X113705Y738994D01* 1131 | X114078Y739153D01* 1132 | X114454Y739304D01* 1133 | X114834Y739445D01* 1134 | X115217Y739577D01* 1135 | X115603Y739699D01* 1136 | X115993Y739812D01* 1137 | X116384Y739915D01* 1138 | X116779Y740009D01* 1139 | X117175Y740093D01* 1140 | X117573Y740167D01* 1141 | X117973Y740231D01* 1142 | X118375Y740286D01* 1143 | X118778Y740330D01* 1144 | X119181Y740365D01* 1145 | X119586Y740390D01* 1146 | X119991Y740405D01* 1147 | X120396Y740410D01* 1148 | X120801Y740405D01* 1149 | X121206Y740390D01* 1150 | X121611Y740365D01* 1151 | X122014Y740330D01* 1152 | X122417Y740286D01* 1153 | X122819Y740231D01* 1154 | X123219Y740167D01* 1155 | X123617Y740093D01* 1156 | X124013Y740009D01* 1157 | X124408Y739915D01* 1158 | X124799Y739812D01* 1159 | X125189Y739699D01* 1160 | X125575Y739577D01* 1161 | X125958Y739445D01* 1162 | X126338Y739304D01* 1163 | X126714Y739153D01* 1164 | X127087Y738994D01* 1165 | X127455Y738825D01* 1166 | X127819Y738647D01* 1167 | X128179Y738461D01* 1168 | X128534Y738265D01* 1169 | X128884Y738061D01* 1170 | X129229Y737849D01* 1171 | X129568Y737628D01* 1172 | X129903Y737398D01* 1173 | X130231Y737161D01* 1174 | X130553Y736916D01* 1175 | X130870Y736662D01* 1176 | X131180Y736402D01* 1177 | X131483Y736133D01* 1178 | X131780Y735857D01* 1179 | X132070Y735574D01* 1180 | X132353Y735284D01* 1181 | X132629Y734987D01* 1182 | X132898Y734684D01* 1183 | X133158Y734374D01* 1184 | X133412Y734057D01* 1185 | X133657Y733735D01* 1186 | X133894Y733407D01* 1187 | X134124Y733072D01* 1188 | X134345Y732733D01* 1189 | X134557Y732388D01* 1190 | X134761Y732038D01* 1191 | X134957Y731683D01* 1192 | X135143Y731323D01* 1193 | X135321Y730959D01* 1194 | X135490Y730591D01* 1195 | X135649Y730218D01* 1196 | X135800Y729842D01* 1197 | X135941Y729462D01* 1198 | X136073Y729079D01* 1199 | X136195Y728693D01* 1200 | X136308Y728303D01* 1201 | X136411Y727912D01* 1202 | X136505Y727517D01* 1203 | X136589Y727121D01* 1204 | X136663Y726723D01* 1205 | X136727Y726323D01* 1206 | X136782Y725921D01* 1207 | X136826Y725518D01* 1208 | X136861Y725115D01* 1209 | X136886Y724710D01* 1210 | X136901Y724305D01* 1211 | X136906Y723900D01* 1212 | X136901Y723495D01* 1213 | X136886Y723090D01* 1214 | X136861Y722685D01* 1215 | X136826Y722282D01* 1216 | X136782Y721879D01* 1217 | X136727Y721477D01* 1218 | X136663Y721077D01* 1219 | X136589Y720679D01* 1220 | X136505Y720283D01* 1221 | X136411Y719888D01* 1222 | X136308Y719497D01* 1223 | X136195Y719107D01* 1224 | X136073Y718721D01* 1225 | X135941Y718338D01* 1226 | X135800Y717958D01* 1227 | X135649Y717582D01* 1228 | X135490Y717209D01* 1229 | X135321Y716841D01* 1230 | X135143Y716477D01* 1231 | X134957Y716117D01* 1232 | X134761Y715762D01* 1233 | X134557Y715412D01* 1234 | X134345Y715067D01* 1235 | X134124Y714728D01* 1236 | X133894Y714393D01* 1237 | X133657Y714065D01* 1238 | X133412Y713743D01* 1239 | X133158Y713426D01* 1240 | X132898Y713116D01* 1241 | X132629Y712813D01* 1242 | X132353Y712516D01* 1243 | X132070Y712226D01* 1244 | X131780Y711943D01* 1245 | X131483Y711667D01* 1246 | X131180Y711398D01* 1247 | X130870Y711138D01* 1248 | X130553Y710884D01* 1249 | X130231Y710639D01* 1250 | X129903Y710402D01* 1251 | X129568Y710172D01* 1252 | X129229Y709951D01* 1253 | X128884Y709739D01* 1254 | X128534Y709535D01* 1255 | X128179Y709339D01* 1256 | X127819Y709153D01* 1257 | X127455Y708975D01* 1258 | X127087Y708806D01* 1259 | X126714Y708647D01* 1260 | X126338Y708496D01* 1261 | X125958Y708355D01* 1262 | X125575Y708223D01* 1263 | X125189Y708101D01* 1264 | X124799Y707988D01* 1265 | X124408Y707885D01* 1266 | X124013Y707791D01* 1267 | X123617Y707707D01* 1268 | X123219Y707633D01* 1269 | X122819Y707569D01* 1270 | X122417Y707514D01* 1271 | X122014Y707470D01* 1272 | X121611Y707435D01* 1273 | X121206Y707410D01* 1274 | X120801Y707395D01* 1275 | X120396Y707390D01* 1276 | X119991Y707395D01* 1277 | X119586Y707410D01* 1278 | X119181Y707435D01* 1279 | X118778Y707470D01* 1280 | X118375Y707514D01* 1281 | X117973Y707569D01* 1282 | X117573Y707633D01* 1283 | X117175Y707707D01* 1284 | X116779Y707791D01* 1285 | X116384Y707885D01* 1286 | X115993Y707988D01* 1287 | X115603Y708101D01* 1288 | X115217Y708223D01* 1289 | X114834Y708355D01* 1290 | X114454Y708496D01* 1291 | X114078Y708647D01* 1292 | X113705Y708806D01* 1293 | X113337Y708975D01* 1294 | X112973Y709153D01* 1295 | X112613Y709339D01* 1296 | X112258Y709535D01* 1297 | X111908Y709739D01* 1298 | X111563Y709951D01* 1299 | X111224Y710172D01* 1300 | X110889Y710402D01* 1301 | X110561Y710639D01* 1302 | X110239Y710884D01* 1303 | X109922Y711138D01* 1304 | X109612Y711398D01* 1305 | X109309Y711667D01* 1306 | X109012Y711943D01* 1307 | X108722Y712226D01* 1308 | X108439Y712516D01* 1309 | X108163Y712813D01* 1310 | X107894Y713116D01* 1311 | X107634Y713426D01* 1312 | X107380Y713743D01* 1313 | X107135Y714065D01* 1314 | X106898Y714393D01* 1315 | X106668Y714728D01* 1316 | X106447Y715067D01* 1317 | X106235Y715412D01* 1318 | X106031Y715762D01* 1319 | X105835Y716117D01* 1320 | X105649Y716477D01* 1321 | X105471Y716841D01* 1322 | X105302Y717209D01* 1323 | X105143Y717582D01* 1324 | X104992Y717958D01* 1325 | X104851Y718338D01* 1326 | X104719Y718721D01* 1327 | X104597Y719107D01* 1328 | X104484Y719497D01* 1329 | X104381Y719888D01* 1330 | X104287Y720283D01* 1331 | X104203Y720679D01* 1332 | X104129Y721077D01* 1333 | X104065Y721477D01* 1334 | X104010Y721879D01* 1335 | X103966Y722282D01* 1336 | X103931Y722685D01* 1337 | X103906Y723090D01* 1338 | X103891Y723495D01* 1339 | X103886Y723900D01* 1340 | X574294Y723900D02* 1341 | X574299Y724305D01* 1342 | X574314Y724710D01* 1343 | X574339Y725115D01* 1344 | X574374Y725518D01* 1345 | X574418Y725921D01* 1346 | X574473Y726323D01* 1347 | X574537Y726723D01* 1348 | X574611Y727121D01* 1349 | X574695Y727517D01* 1350 | X574789Y727912D01* 1351 | X574892Y728303D01* 1352 | X575005Y728693D01* 1353 | X575127Y729079D01* 1354 | X575259Y729462D01* 1355 | X575400Y729842D01* 1356 | X575551Y730218D01* 1357 | X575710Y730591D01* 1358 | X575879Y730959D01* 1359 | X576057Y731323D01* 1360 | X576243Y731683D01* 1361 | X576439Y732038D01* 1362 | X576643Y732388D01* 1363 | X576855Y732733D01* 1364 | X577076Y733072D01* 1365 | X577306Y733407D01* 1366 | X577543Y733735D01* 1367 | X577788Y734057D01* 1368 | X578042Y734374D01* 1369 | X578302Y734684D01* 1370 | X578571Y734987D01* 1371 | X578847Y735284D01* 1372 | X579130Y735574D01* 1373 | X579420Y735857D01* 1374 | X579717Y736133D01* 1375 | X580020Y736402D01* 1376 | X580330Y736662D01* 1377 | X580647Y736916D01* 1378 | X580969Y737161D01* 1379 | X581297Y737398D01* 1380 | X581632Y737628D01* 1381 | X581971Y737849D01* 1382 | X582316Y738061D01* 1383 | X582666Y738265D01* 1384 | X583021Y738461D01* 1385 | X583381Y738647D01* 1386 | X583745Y738825D01* 1387 | X584113Y738994D01* 1388 | X584486Y739153D01* 1389 | X584862Y739304D01* 1390 | X585242Y739445D01* 1391 | X585625Y739577D01* 1392 | X586011Y739699D01* 1393 | X586401Y739812D01* 1394 | X586792Y739915D01* 1395 | X587187Y740009D01* 1396 | X587583Y740093D01* 1397 | X587981Y740167D01* 1398 | X588381Y740231D01* 1399 | X588783Y740286D01* 1400 | X589186Y740330D01* 1401 | X589589Y740365D01* 1402 | X589994Y740390D01* 1403 | X590399Y740405D01* 1404 | X590804Y740410D01* 1405 | X591209Y740405D01* 1406 | X591614Y740390D01* 1407 | X592019Y740365D01* 1408 | X592422Y740330D01* 1409 | X592825Y740286D01* 1410 | X593227Y740231D01* 1411 | X593627Y740167D01* 1412 | X594025Y740093D01* 1413 | X594421Y740009D01* 1414 | X594816Y739915D01* 1415 | X595207Y739812D01* 1416 | X595597Y739699D01* 1417 | X595983Y739577D01* 1418 | X596366Y739445D01* 1419 | X596746Y739304D01* 1420 | X597122Y739153D01* 1421 | X597495Y738994D01* 1422 | X597863Y738825D01* 1423 | X598227Y738647D01* 1424 | X598587Y738461D01* 1425 | X598942Y738265D01* 1426 | X599292Y738061D01* 1427 | X599637Y737849D01* 1428 | X599976Y737628D01* 1429 | X600311Y737398D01* 1430 | X600639Y737161D01* 1431 | X600961Y736916D01* 1432 | X601278Y736662D01* 1433 | X601588Y736402D01* 1434 | X601891Y736133D01* 1435 | X602188Y735857D01* 1436 | X602478Y735574D01* 1437 | X602761Y735284D01* 1438 | X603037Y734987D01* 1439 | X603306Y734684D01* 1440 | X603566Y734374D01* 1441 | X603820Y734057D01* 1442 | X604065Y733735D01* 1443 | X604302Y733407D01* 1444 | X604532Y733072D01* 1445 | X604753Y732733D01* 1446 | X604965Y732388D01* 1447 | X605169Y732038D01* 1448 | X605365Y731683D01* 1449 | X605551Y731323D01* 1450 | X605729Y730959D01* 1451 | X605898Y730591D01* 1452 | X606057Y730218D01* 1453 | X606208Y729842D01* 1454 | X606349Y729462D01* 1455 | X606481Y729079D01* 1456 | X606603Y728693D01* 1457 | X606716Y728303D01* 1458 | X606819Y727912D01* 1459 | X606913Y727517D01* 1460 | X606997Y727121D01* 1461 | X607071Y726723D01* 1462 | X607135Y726323D01* 1463 | X607190Y725921D01* 1464 | X607234Y725518D01* 1465 | X607269Y725115D01* 1466 | X607294Y724710D01* 1467 | X607309Y724305D01* 1468 | X607314Y723900D01* 1469 | X607309Y723495D01* 1470 | X607294Y723090D01* 1471 | X607269Y722685D01* 1472 | X607234Y722282D01* 1473 | X607190Y721879D01* 1474 | X607135Y721477D01* 1475 | X607071Y721077D01* 1476 | X606997Y720679D01* 1477 | X606913Y720283D01* 1478 | X606819Y719888D01* 1479 | X606716Y719497D01* 1480 | X606603Y719107D01* 1481 | X606481Y718721D01* 1482 | X606349Y718338D01* 1483 | X606208Y717958D01* 1484 | X606057Y717582D01* 1485 | X605898Y717209D01* 1486 | X605729Y716841D01* 1487 | X605551Y716477D01* 1488 | X605365Y716117D01* 1489 | X605169Y715762D01* 1490 | X604965Y715412D01* 1491 | X604753Y715067D01* 1492 | X604532Y714728D01* 1493 | X604302Y714393D01* 1494 | X604065Y714065D01* 1495 | X603820Y713743D01* 1496 | X603566Y713426D01* 1497 | X603306Y713116D01* 1498 | X603037Y712813D01* 1499 | X602761Y712516D01* 1500 | X602478Y712226D01* 1501 | X602188Y711943D01* 1502 | X601891Y711667D01* 1503 | X601588Y711398D01* 1504 | X601278Y711138D01* 1505 | X600961Y710884D01* 1506 | X600639Y710639D01* 1507 | X600311Y710402D01* 1508 | X599976Y710172D01* 1509 | X599637Y709951D01* 1510 | X599292Y709739D01* 1511 | X598942Y709535D01* 1512 | X598587Y709339D01* 1513 | X598227Y709153D01* 1514 | X597863Y708975D01* 1515 | X597495Y708806D01* 1516 | X597122Y708647D01* 1517 | X596746Y708496D01* 1518 | X596366Y708355D01* 1519 | X595983Y708223D01* 1520 | X595597Y708101D01* 1521 | X595207Y707988D01* 1522 | X594816Y707885D01* 1523 | X594421Y707791D01* 1524 | X594025Y707707D01* 1525 | X593627Y707633D01* 1526 | X593227Y707569D01* 1527 | X592825Y707514D01* 1528 | X592422Y707470D01* 1529 | X592019Y707435D01* 1530 | X591614Y707410D01* 1531 | X591209Y707395D01* 1532 | X590804Y707390D01* 1533 | X590399Y707395D01* 1534 | X589994Y707410D01* 1535 | X589589Y707435D01* 1536 | X589186Y707470D01* 1537 | X588783Y707514D01* 1538 | X588381Y707569D01* 1539 | X587981Y707633D01* 1540 | X587583Y707707D01* 1541 | X587187Y707791D01* 1542 | X586792Y707885D01* 1543 | X586401Y707988D01* 1544 | X586011Y708101D01* 1545 | X585625Y708223D01* 1546 | X585242Y708355D01* 1547 | X584862Y708496D01* 1548 | X584486Y708647D01* 1549 | X584113Y708806D01* 1550 | X583745Y708975D01* 1551 | X583381Y709153D01* 1552 | X583021Y709339D01* 1553 | X582666Y709535D01* 1554 | X582316Y709739D01* 1555 | X581971Y709951D01* 1556 | X581632Y710172D01* 1557 | X581297Y710402D01* 1558 | X580969Y710639D01* 1559 | X580647Y710884D01* 1560 | X580330Y711138D01* 1561 | X580020Y711398D01* 1562 | X579717Y711667D01* 1563 | X579420Y711943D01* 1564 | X579130Y712226D01* 1565 | X578847Y712516D01* 1566 | X578571Y712813D01* 1567 | X578302Y713116D01* 1568 | X578042Y713426D01* 1569 | X577788Y713743D01* 1570 | X577543Y714065D01* 1571 | X577306Y714393D01* 1572 | X577076Y714728D01* 1573 | X576855Y715067D01* 1574 | X576643Y715412D01* 1575 | X576439Y715762D01* 1576 | X576243Y716117D01* 1577 | X576057Y716477D01* 1578 | X575879Y716841D01* 1579 | X575710Y717209D01* 1580 | X575551Y717582D01* 1581 | X575400Y717958D01* 1582 | X575259Y718338D01* 1583 | X575127Y718721D01* 1584 | X575005Y719107D01* 1585 | X574892Y719497D01* 1586 | X574789Y719888D01* 1587 | X574695Y720283D01* 1588 | X574611Y720679D01* 1589 | X574537Y721077D01* 1590 | X574473Y721477D01* 1591 | X574418Y721879D01* 1592 | X574374Y722282D01* 1593 | X574339Y722685D01* 1594 | X574314Y723090D01* 1595 | X574299Y723495D01* 1596 | X574294Y723900D01* 1597 | D16* 1598 | X160020Y716915D02* 1599 | X156845Y714375D01* 1600 | X160020Y716915D02* 1601 | X160020Y705485D01* 1602 | X156845Y705485D02* 1603 | X163195Y705485D01* 1604 | X168275Y705485D02* 1605 | X171450Y705485D01* 1606 | X171561Y705487D01* 1607 | X171671Y705493D01* 1608 | X171782Y705502D01* 1609 | X171892Y705516D01* 1610 | X172001Y705533D01* 1611 | X172110Y705554D01* 1612 | X172218Y705579D01* 1613 | X172325Y705608D01* 1614 | X172431Y705640D01* 1615 | X172536Y705676D01* 1616 | X172639Y705716D01* 1617 | X172741Y705759D01* 1618 | X172842Y705806D01* 1619 | X172941Y705857D01* 1620 | X173037Y705910D01* 1621 | X173132Y705967D01* 1622 | X173225Y706028D01* 1623 | X173316Y706091D01* 1624 | X173405Y706158D01* 1625 | X173491Y706228D01* 1626 | X173574Y706301D01* 1627 | X173656Y706376D01* 1628 | X173734Y706454D01* 1629 | X173809Y706536D01* 1630 | X173882Y706619D01* 1631 | X173952Y706705D01* 1632 | X174019Y706794D01* 1633 | X174082Y706885D01* 1634 | X174143Y706978D01* 1635 | X174200Y707073D01* 1636 | X174253Y707169D01* 1637 | X174304Y707268D01* 1638 | X174351Y707369D01* 1639 | X174394Y707471D01* 1640 | X174434Y707574D01* 1641 | X174470Y707679D01* 1642 | X174502Y707785D01* 1643 | X174531Y707892D01* 1644 | X174556Y708000D01* 1645 | X174577Y708109D01* 1646 | X174594Y708218D01* 1647 | X174608Y708328D01* 1648 | X174617Y708439D01* 1649 | X174623Y708549D01* 1650 | X174625Y708660D01* 1651 | X174623Y708771D01* 1652 | X174617Y708881D01* 1653 | X174608Y708992D01* 1654 | X174594Y709102D01* 1655 | X174577Y709211D01* 1656 | X174556Y709320D01* 1657 | X174531Y709428D01* 1658 | X174502Y709535D01* 1659 | X174470Y709641D01* 1660 | X174434Y709746D01* 1661 | X174394Y709849D01* 1662 | X174351Y709951D01* 1663 | X174304Y710052D01* 1664 | X174253Y710151D01* 1665 | X174200Y710247D01* 1666 | X174143Y710342D01* 1667 | X174082Y710435D01* 1668 | X174019Y710526D01* 1669 | X173952Y710615D01* 1670 | X173882Y710701D01* 1671 | X173809Y710784D01* 1672 | X173734Y710866D01* 1673 | X173656Y710944D01* 1674 | X173574Y711019D01* 1675 | X173491Y711092D01* 1676 | X173405Y711162D01* 1677 | X173316Y711229D01* 1678 | X173225Y711292D01* 1679 | X173132Y711353D01* 1680 | X173038Y711410D01* 1681 | X172941Y711463D01* 1682 | X172842Y711514D01* 1683 | X172741Y711561D01* 1684 | X172639Y711604D01* 1685 | X172536Y711644D01* 1686 | X172431Y711680D01* 1687 | X172325Y711712D01* 1688 | X172218Y711741D01* 1689 | X172110Y711766D01* 1690 | X172001Y711787D01* 1691 | X171892Y711804D01* 1692 | X171782Y711818D01* 1693 | X171671Y711827D01* 1694 | X171561Y711833D01* 1695 | X171450Y711835D01* 1696 | X172085Y716915D02* 1697 | X168275Y716915D01* 1698 | X172085Y716915D02* 1699 | X172185Y716913D01* 1700 | X172284Y716907D01* 1701 | X172384Y716897D01* 1702 | X172482Y716884D01* 1703 | X172581Y716866D01* 1704 | X172678Y716845D01* 1705 | X172774Y716820D01* 1706 | X172870Y716791D01* 1707 | X172964Y716758D01* 1708 | X173057Y716722D01* 1709 | X173148Y716682D01* 1710 | X173238Y716638D01* 1711 | X173326Y716591D01* 1712 | X173412Y716541D01* 1713 | X173496Y716487D01* 1714 | X173578Y716430D01* 1715 | X173657Y716370D01* 1716 | X173735Y716306D01* 1717 | X173809Y716240D01* 1718 | X173881Y716171D01* 1719 | X173950Y716099D01* 1720 | X174016Y716025D01* 1721 | X174080Y715947D01* 1722 | X174140Y715868D01* 1723 | X174197Y715786D01* 1724 | X174251Y715702D01* 1725 | X174301Y715616D01* 1726 | X174348Y715528D01* 1727 | X174392Y715438D01* 1728 | X174432Y715347D01* 1729 | X174468Y715254D01* 1730 | X174501Y715160D01* 1731 | X174530Y715064D01* 1732 | X174555Y714968D01* 1733 | X174576Y714871D01* 1734 | X174594Y714772D01* 1735 | X174607Y714674D01* 1736 | X174617Y714574D01* 1737 | X174623Y714475D01* 1738 | X174625Y714375D01* 1739 | X174623Y714275D01* 1740 | X174617Y714176D01* 1741 | X174607Y714076D01* 1742 | X174594Y713978D01* 1743 | X174576Y713879D01* 1744 | X174555Y713782D01* 1745 | X174530Y713686D01* 1746 | X174501Y713590D01* 1747 | X174468Y713496D01* 1748 | X174432Y713403D01* 1749 | X174392Y713312D01* 1750 | X174348Y713222D01* 1751 | X174301Y713134D01* 1752 | X174251Y713048D01* 1753 | X174197Y712964D01* 1754 | X174140Y712882D01* 1755 | X174080Y712803D01* 1756 | X174016Y712725D01* 1757 | X173950Y712651D01* 1758 | X173881Y712579D01* 1759 | X173809Y712510D01* 1760 | X173735Y712444D01* 1761 | X173657Y712380D01* 1762 | X173578Y712320D01* 1763 | X173496Y712263D01* 1764 | X173412Y712209D01* 1765 | X173326Y712159D01* 1766 | X173238Y712112D01* 1767 | X173148Y712068D01* 1768 | X173057Y712028D01* 1769 | X172964Y711992D01* 1770 | X172870Y711959D01* 1771 | X172774Y711930D01* 1772 | X172678Y711905D01* 1773 | X172581Y711884D01* 1774 | X172482Y711866D01* 1775 | X172384Y711853D01* 1776 | X172284Y711843D01* 1777 | X172185Y711837D01* 1778 | X172085Y711835D01* 1779 | X169545Y711835D01* 1780 | X534035Y714375D02* 1781 | X537210Y716915D01* 1782 | X537210Y705485D01* 1783 | X534035Y705485D02* 1784 | X540385Y705485D01* 1785 | X167006Y739458D02* 1786 | X167004Y739562D01* 1787 | X166998Y739667D01* 1788 | X166989Y739771D01* 1789 | X166976Y739874D01* 1790 | X166958Y739977D01* 1791 | X166938Y740079D01* 1792 | X166913Y740181D01* 1793 | X166885Y740281D01* 1794 | X166853Y740381D01* 1795 | X166817Y740479D01* 1796 | X166778Y740576D01* 1797 | X166736Y740671D01* 1798 | X166690Y740765D01* 1799 | X166640Y740857D01* 1800 | X166588Y740947D01* 1801 | X166532Y741035D01* 1802 | X166472Y741121D01* 1803 | X166410Y741205D01* 1804 | X166345Y741286D01* 1805 | X166277Y741365D01* 1806 | X166205Y741442D01* 1807 | X166132Y741515D01* 1808 | X166055Y741587D01* 1809 | X165976Y741655D01* 1810 | X165895Y741720D01* 1811 | X165811Y741782D01* 1812 | X165725Y741842D01* 1813 | X165637Y741898D01* 1814 | X165547Y741950D01* 1815 | X165455Y742000D01* 1816 | X165361Y742046D01* 1817 | X165266Y742088D01* 1818 | X165169Y742127D01* 1819 | X165071Y742163D01* 1820 | X164971Y742195D01* 1821 | X164871Y742223D01* 1822 | X164769Y742248D01* 1823 | X164667Y742268D01* 1824 | X164564Y742286D01* 1825 | X164461Y742299D01* 1826 | X164357Y742308D01* 1827 | X164252Y742314D01* 1828 | X164148Y742316D01* 1829 | X164148Y742315D02* 1830 | X164030Y742313D01* 1831 | X163911Y742307D01* 1832 | X163793Y742298D01* 1833 | X163676Y742285D01* 1834 | X163559Y742267D01* 1835 | X163442Y742247D01* 1836 | X163326Y742222D01* 1837 | X163211Y742194D01* 1838 | X163098Y742161D01* 1839 | X162985Y742126D01* 1840 | X162873Y742086D01* 1841 | X162763Y742044D01* 1842 | X162654Y741997D01* 1843 | X162546Y741947D01* 1844 | X162441Y741894D01* 1845 | X162337Y741837D01* 1846 | X162235Y741777D01* 1847 | X162135Y741714D01* 1848 | X162037Y741647D01* 1849 | X161941Y741578D01* 1850 | X161848Y741505D01* 1851 | X161757Y741429D01* 1852 | X161668Y741351D01* 1853 | X161582Y741269D01* 1854 | X161499Y741185D01* 1855 | X161418Y741099D01* 1856 | X161341Y741009D01* 1857 | X161266Y740918D01* 1858 | X161194Y740824D01* 1859 | X161125Y740727D01* 1860 | X161060Y740629D01* 1861 | X160997Y740528D01* 1862 | X160938Y740425D01* 1863 | X160882Y740321D01* 1864 | X160830Y740215D01* 1865 | X160781Y740107D01* 1866 | X160736Y739998D01* 1867 | X160694Y739887D01* 1868 | X160656Y739775D01* 1869 | X166053Y737236D02* 1870 | X166129Y737311D01* 1871 | X166204Y737390D01* 1872 | X166275Y737471D01* 1873 | X166344Y737555D01* 1874 | X166409Y737641D01* 1875 | X166471Y737729D01* 1876 | X166531Y737819D01* 1877 | X166587Y737911D01* 1878 | X166640Y738006D01* 1879 | X166689Y738102D01* 1880 | X166735Y738200D01* 1881 | X166778Y738299D01* 1882 | X166817Y738400D01* 1883 | X166852Y738502D01* 1884 | X166884Y738605D01* 1885 | X166912Y738709D01* 1886 | X166937Y738814D01* 1887 | X166958Y738921D01* 1888 | X166975Y739027D01* 1889 | X166988Y739134D01* 1890 | X166997Y739242D01* 1891 | X167003Y739350D01* 1892 | X167005Y739458D01* 1893 | X166053Y737235D02* 1894 | X160655Y730885D01* 1895 | X167005Y730885D01* 1896 | X172085Y730885D02* 1897 | X175895Y730885D01* 1898 | X175995Y730887D01* 1899 | X176094Y730893D01* 1900 | X176194Y730903D01* 1901 | X176292Y730916D01* 1902 | X176391Y730934D01* 1903 | X176488Y730955D01* 1904 | X176584Y730980D01* 1905 | X176680Y731009D01* 1906 | X176774Y731042D01* 1907 | X176867Y731078D01* 1908 | X176958Y731118D01* 1909 | X177048Y731162D01* 1910 | X177136Y731209D01* 1911 | X177222Y731259D01* 1912 | X177306Y731313D01* 1913 | X177388Y731370D01* 1914 | X177467Y731430D01* 1915 | X177545Y731494D01* 1916 | X177619Y731560D01* 1917 | X177691Y731629D01* 1918 | X177760Y731701D01* 1919 | X177826Y731775D01* 1920 | X177890Y731853D01* 1921 | X177950Y731932D01* 1922 | X178007Y732014D01* 1923 | X178061Y732098D01* 1924 | X178111Y732184D01* 1925 | X178158Y732272D01* 1926 | X178202Y732362D01* 1927 | X178242Y732453D01* 1928 | X178278Y732546D01* 1929 | X178311Y732640D01* 1930 | X178340Y732736D01* 1931 | X178365Y732832D01* 1932 | X178386Y732929D01* 1933 | X178404Y733028D01* 1934 | X178417Y733126D01* 1935 | X178427Y733226D01* 1936 | X178433Y733325D01* 1937 | X178435Y733425D01* 1938 | X178435Y734695D01* 1939 | X178433Y734795D01* 1940 | X178427Y734894D01* 1941 | X178417Y734994D01* 1942 | X178404Y735092D01* 1943 | X178386Y735191D01* 1944 | X178365Y735288D01* 1945 | X178340Y735384D01* 1946 | X178311Y735480D01* 1947 | X178278Y735574D01* 1948 | X178242Y735667D01* 1949 | X178202Y735758D01* 1950 | X178158Y735848D01* 1951 | X178111Y735936D01* 1952 | X178061Y736022D01* 1953 | X178007Y736106D01* 1954 | X177950Y736188D01* 1955 | X177890Y736267D01* 1956 | X177826Y736345D01* 1957 | X177760Y736419D01* 1958 | X177691Y736491D01* 1959 | X177619Y736560D01* 1960 | X177545Y736626D01* 1961 | X177467Y736690D01* 1962 | X177388Y736750D01* 1963 | X177306Y736807D01* 1964 | X177222Y736861D01* 1965 | X177136Y736911D01* 1966 | X177048Y736958D01* 1967 | X176958Y737002D01* 1968 | X176867Y737042D01* 1969 | X176774Y737078D01* 1970 | X176680Y737111D01* 1971 | X176584Y737140D01* 1972 | X176488Y737165D01* 1973 | X176391Y737186D01* 1974 | X176292Y737204D01* 1975 | X176194Y737217D01* 1976 | X176094Y737227D01* 1977 | X175995Y737233D01* 1978 | X175895Y737235D01* 1979 | X172085Y737235D01* 1980 | X172085Y742315D01* 1981 | X178435Y742315D01* 1982 | X534035Y739775D02* 1983 | X537210Y742315D01* 1984 | X537210Y730885D01* 1985 | X534035Y730885D02* 1986 | X540385Y730885D01* 1987 | X545465Y733425D02* 1988 | X548005Y742315D01* 1989 | X545465Y733425D02* 1990 | X551815Y733425D01* 1991 | X549910Y735965D02* 1992 | X549910Y730885D01* 1993 | D15* 1994 | X492760Y683260D02* 1995 | X492760Y657860D01* 1996 | X213360Y657860D02* 1997 | X213360Y683260D01* 1998 | X219710Y664210D02* 1999 | X213360Y657860D01* 2000 | X219710Y676910D02* 2001 | X213360Y683260D01* 2002 | X232410Y676910D02* 2003 | X238760Y683260D01* 2004 | X492760Y683260D01* 2005 | X238760Y657860D02* 2006 | X232410Y664210D01* 2007 | X238760Y657860D02* 2008 | X213360Y657860D01* 2009 | X238760Y657860D02* 2010 | X238760Y683260D01* 2011 | X213360Y683260D01* 2012 | X238760Y657860D02* 2013 | X492760Y657860D01* 2014 | D17* 2015 | X563880Y411480D02* 2016 | X106680Y411480D01* 2017 | X563880Y411480D02* 2018 | X564494Y411487D01* 2019 | X565107Y411510D01* 2020 | X565720Y411547D01* 2021 | X566331Y411599D01* 2022 | X566942Y411665D01* 2023 | X567550Y411747D01* 2024 | X568156Y411843D01* 2025 | X568760Y411953D01* 2026 | X569361Y412078D01* 2027 | X569959Y412218D01* 2028 | X570553Y412372D01* 2029 | X571143Y412541D01* 2030 | X571729Y412723D01* 2031 | X572310Y412920D01* 2032 | X572887Y413131D01* 2033 | X573458Y413355D01* 2034 | X574024Y413593D01* 2035 | X574584Y413845D01* 2036 | X575137Y414111D01* 2037 | X575684Y414389D01* 2038 | X576224Y414681D01* 2039 | X576757Y414986D01* 2040 | X577282Y415304D01* 2041 | X577800Y415634D01* 2042 | X578309Y415976D01* 2043 | X578810Y416331D01* 2044 | X579302Y416698D01* 2045 | X579785Y417076D01* 2046 | X580259Y417466D01* 2047 | X580723Y417868D01* 2048 | X581178Y418280D01* 2049 | X581622Y418704D01* 2050 | X582056Y419138D01* 2051 | X582480Y419582D01* 2052 | X582892Y420037D01* 2053 | X583294Y420501D01* 2054 | X583684Y420975D01* 2055 | X584062Y421458D01* 2056 | X584429Y421950D01* 2057 | X584784Y422451D01* 2058 | X585126Y422960D01* 2059 | X585456Y423478D01* 2060 | X585774Y424003D01* 2061 | X586079Y424536D01* 2062 | X586371Y425076D01* 2063 | X586649Y425623D01* 2064 | X586915Y426176D01* 2065 | X587167Y426736D01* 2066 | X587405Y427302D01* 2067 | X587629Y427873D01* 2068 | X587840Y428450D01* 2069 | X588037Y429031D01* 2070 | X588219Y429617D01* 2071 | X588388Y430207D01* 2072 | X588542Y430801D01* 2073 | X588682Y431399D01* 2074 | X588807Y432000D01* 2075 | X588917Y432604D01* 2076 | X589013Y433210D01* 2077 | X589095Y433818D01* 2078 | X589161Y434429D01* 2079 | X589213Y435040D01* 2080 | X589250Y435653D01* 2081 | X589273Y436266D01* 2082 | X589280Y436880D01* 2083 | X589280Y614680D01* 2084 | X589273Y615294D01* 2085 | X589250Y615907D01* 2086 | X589213Y616520D01* 2087 | X589161Y617131D01* 2088 | X589095Y617742D01* 2089 | X589013Y618350D01* 2090 | X588917Y618956D01* 2091 | X588807Y619560D01* 2092 | X588682Y620161D01* 2093 | X588542Y620759D01* 2094 | X588388Y621353D01* 2095 | X588219Y621943D01* 2096 | X588037Y622529D01* 2097 | X587840Y623110D01* 2098 | X587629Y623687D01* 2099 | X587405Y624258D01* 2100 | X587167Y624824D01* 2101 | X586915Y625384D01* 2102 | X586649Y625937D01* 2103 | X586371Y626484D01* 2104 | X586079Y627024D01* 2105 | X585774Y627557D01* 2106 | X585456Y628082D01* 2107 | X585126Y628600D01* 2108 | X584784Y629109D01* 2109 | X584429Y629610D01* 2110 | X584062Y630102D01* 2111 | X583684Y630585D01* 2112 | X583294Y631059D01* 2113 | X582892Y631523D01* 2114 | X582480Y631978D01* 2115 | X582056Y632422D01* 2116 | X581622Y632856D01* 2117 | X581178Y633280D01* 2118 | X580723Y633692D01* 2119 | X580259Y634094D01* 2120 | X579785Y634484D01* 2121 | X579302Y634862D01* 2122 | X578810Y635229D01* 2123 | X578309Y635584D01* 2124 | X577800Y635926D01* 2125 | X577282Y636256D01* 2126 | X576757Y636574D01* 2127 | X576224Y636879D01* 2128 | X575684Y637171D01* 2129 | X575137Y637449D01* 2130 | X574584Y637715D01* 2131 | X574024Y637967D01* 2132 | X573458Y638205D01* 2133 | X572887Y638429D01* 2134 | X572310Y638640D01* 2135 | X571729Y638837D01* 2136 | X571143Y639019D01* 2137 | X570553Y639188D01* 2138 | X569959Y639342D01* 2139 | X569361Y639482D01* 2140 | X568760Y639607D01* 2141 | X568156Y639717D01* 2142 | X567550Y639813D01* 2143 | X566942Y639895D01* 2144 | X566331Y639961D01* 2145 | X565720Y640013D01* 2146 | X565107Y640050D01* 2147 | X564494Y640073D01* 2148 | X563880Y640080D01* 2149 | X106680Y640080D01* 2150 | X106066Y640073D01* 2151 | X105453Y640050D01* 2152 | X104840Y640013D01* 2153 | X104229Y639961D01* 2154 | X103618Y639895D01* 2155 | X103010Y639813D01* 2156 | X102404Y639717D01* 2157 | X101800Y639607D01* 2158 | X101199Y639482D01* 2159 | X100601Y639342D01* 2160 | X100007Y639188D01* 2161 | X99417Y639019D01* 2162 | X98831Y638837D01* 2163 | X98250Y638640D01* 2164 | X97673Y638429D01* 2165 | X97102Y638205D01* 2166 | X96536Y637967D01* 2167 | X95976Y637715D01* 2168 | X95423Y637449D01* 2169 | X94876Y637171D01* 2170 | X94336Y636879D01* 2171 | X93803Y636574D01* 2172 | X93278Y636256D01* 2173 | X92760Y635926D01* 2174 | X92251Y635584D01* 2175 | X91750Y635229D01* 2176 | X91258Y634862D01* 2177 | X90775Y634484D01* 2178 | X90301Y634094D01* 2179 | X89837Y633692D01* 2180 | X89382Y633280D01* 2181 | X88938Y632856D01* 2182 | X88504Y632422D01* 2183 | X88080Y631978D01* 2184 | X87668Y631523D01* 2185 | X87266Y631059D01* 2186 | X86876Y630585D01* 2187 | X86498Y630102D01* 2188 | X86131Y629610D01* 2189 | X85776Y629109D01* 2190 | X85434Y628600D01* 2191 | X85104Y628082D01* 2192 | X84786Y627557D01* 2193 | X84481Y627024D01* 2194 | X84189Y626484D01* 2195 | X83911Y625937D01* 2196 | X83645Y625384D01* 2197 | X83393Y624824D01* 2198 | X83155Y624258D01* 2199 | X82931Y623687D01* 2200 | X82720Y623110D01* 2201 | X82523Y622529D01* 2202 | X82341Y621943D01* 2203 | X82172Y621353D01* 2204 | X82018Y620759D01* 2205 | X81878Y620161D01* 2206 | X81753Y619560D01* 2207 | X81643Y618956D01* 2208 | X81547Y618350D01* 2209 | X81465Y617742D01* 2210 | X81399Y617131D01* 2211 | X81347Y616520D01* 2212 | X81310Y615907D01* 2213 | X81287Y615294D01* 2214 | X81280Y614680D01* 2215 | X81280Y548640D01* 2216 | X86360Y543560D01* 2217 | X86360Y508000D01* 2218 | X81280Y502920D01* 2219 | X81280Y436880D01* 2220 | X81287Y436266D01* 2221 | X81310Y435653D01* 2222 | X81347Y435040D01* 2223 | X81399Y434429D01* 2224 | X81465Y433818D01* 2225 | X81547Y433210D01* 2226 | X81643Y432604D01* 2227 | X81753Y432000D01* 2228 | X81878Y431399D01* 2229 | X82018Y430801D01* 2230 | X82172Y430207D01* 2231 | X82341Y429617D01* 2232 | X82523Y429031D01* 2233 | X82720Y428450D01* 2234 | X82931Y427873D01* 2235 | X83155Y427302D01* 2236 | X83393Y426736D01* 2237 | X83645Y426176D01* 2238 | X83911Y425623D01* 2239 | X84189Y425076D01* 2240 | X84481Y424536D01* 2241 | X84786Y424003D01* 2242 | X85104Y423478D01* 2243 | X85434Y422960D01* 2244 | X85776Y422451D01* 2245 | X86131Y421950D01* 2246 | X86498Y421458D01* 2247 | X86876Y420975D01* 2248 | X87266Y420501D01* 2249 | X87668Y420037D01* 2250 | X88080Y419582D01* 2251 | X88504Y419138D01* 2252 | X88938Y418704D01* 2253 | X89382Y418280D01* 2254 | X89837Y417868D01* 2255 | X90301Y417466D01* 2256 | X90775Y417076D01* 2257 | X91258Y416698D01* 2258 | X91750Y416331D01* 2259 | X92251Y415976D01* 2260 | X92760Y415634D01* 2261 | X93278Y415304D01* 2262 | X93803Y414986D01* 2263 | X94336Y414681D01* 2264 | X94876Y414389D01* 2265 | X95423Y414111D01* 2266 | X95976Y413845D01* 2267 | X96536Y413593D01* 2268 | X97102Y413355D01* 2269 | X97673Y413131D01* 2270 | X98250Y412920D01* 2271 | X98831Y412723D01* 2272 | X99417Y412541D01* 2273 | X100007Y412372D01* 2274 | X100601Y412218D01* 2275 | X101199Y412078D01* 2276 | X101800Y411953D01* 2277 | X102404Y411843D01* 2278 | X103010Y411747D01* 2279 | X103618Y411665D01* 2280 | X104229Y411599D01* 2281 | X104840Y411547D01* 2282 | X105453Y411510D01* 2283 | X106066Y411487D01* 2284 | X106680Y411480D01* 2285 | M02* 2286 | -------------------------------------------------------------------------------- /pcb/gerber/soldermask_bottom.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INSoldermask Bottom*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | %ADD10C,1.703200*% 12 | %ADD11R,1.703200X1.703200*% 13 | %ADD12P,1.869504X8X22.500000*% 14 | %ADD13C,5.283200*% 15 | %ADD14C,1.524000*% 16 | %ADD15C,1.981200*% 17 | %ADD16C,4.394200*% 18 | 19 | 20 | D10* 21 | X521970Y566420D03* 22 | X496570Y566420D03* 23 | X471170Y566420D03* 24 | X445770Y566420D03* 25 | X420370Y566420D03* 26 | X394970Y566420D03* 27 | X369570Y566420D03* 28 | X344170Y566420D03* 29 | X318770Y566420D03* 30 | X293370Y566420D03* 31 | X267970Y566420D03* 32 | X242570Y566420D03* 33 | X217170Y566420D03* 34 | X191770Y566420D03* 35 | X521970Y490220D03* 36 | X496570Y490220D03* 37 | X471170Y490220D03* 38 | X445770Y490220D03* 39 | X420370Y490220D03* 40 | X394970Y490220D03* 41 | X369570Y490220D03* 42 | X344170Y490220D03* 43 | X318770Y490220D03* 44 | X293370Y490220D03* 45 | X267970Y490220D03* 46 | X242570Y490220D03* 47 | X217170Y490220D03* 48 | D11* 49 | X191770Y490220D03* 50 | D12* 51 | X189992Y711200D03* 52 | X217678Y711200D03* 53 | X245110Y711200D03* 54 | X272796Y711200D03* 55 | X300482Y711200D03* 56 | X327914Y711200D03* 57 | X355600Y711200D03* 58 | X383286Y711200D03* 59 | X410718Y711200D03* 60 | X438404Y711200D03* 61 | X466090Y711200D03* 62 | X493522Y711200D03* 63 | X521208Y711200D03* 64 | X203708Y736600D03* 65 | X231394Y736600D03* 66 | X259080Y736600D03* 67 | X286512Y736600D03* 68 | X314198Y736600D03* 69 | X341884Y736600D03* 70 | X369316Y736600D03* 71 | X397002Y736600D03* 72 | X424688Y736600D03* 73 | X452120Y736600D03* 74 | X479806Y736600D03* 75 | X507492Y736600D03* 76 | D13* 77 | X120396Y723900D03* 78 | X590804Y723900D03* 79 | D14* 80 | X226060Y677164D02* 81 | X226060Y663956D01* 82 | X251460Y663956D02* 83 | X251460Y677164D01* 84 | X276860Y677164D02* 85 | X276860Y663956D01* 86 | X302260Y663956D02* 87 | X302260Y677164D01* 88 | X327660Y677164D02* 89 | X327660Y663956D01* 90 | X353060Y663956D02* 91 | X353060Y677164D01* 92 | X378460Y677164D02* 93 | X378460Y663956D01* 94 | X403860Y663956D02* 95 | X403860Y677164D01* 96 | X429260Y677164D02* 97 | X429260Y663956D01* 98 | X454660Y663956D02* 99 | X454660Y677164D01* 100 | X480060Y677164D02* 101 | X480060Y663956D01* 102 | D15* 103 | X246380Y627380D03* 104 | X271780Y627380D03* 105 | X297180Y627380D03* 106 | X322580Y627380D03* 107 | X347980Y627380D03* 108 | X373380Y627380D03* 109 | X398780Y627380D03* 110 | X424180Y627380D03* 111 | X449580Y627380D03* 112 | X474980Y627380D03* 113 | X500380Y627380D03* 114 | X525780Y627380D03* 115 | X246380Y424180D03* 116 | X271780Y424180D03* 117 | X297180Y424180D03* 118 | X322580Y424180D03* 119 | X347980Y424180D03* 120 | X373380Y424180D03* 121 | X398780Y424180D03* 122 | X424180Y424180D03* 123 | X449580Y424180D03* 124 | X474980Y424180D03* 125 | X500380Y424180D03* 126 | X525780Y424180D03* 127 | X220980Y424180D03* 128 | X195580Y424180D03* 129 | X170180Y424180D03* 130 | X144780Y424180D03* 131 | D16* 132 | X544830Y669290D03* 133 | X165100Y669290D03* 134 | M02* 135 | -------------------------------------------------------------------------------- /pcb/gerber/soldermask_top.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INSoldermask Top*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | %ADD10C,1.703200*% 12 | %ADD11R,1.703200X1.703200*% 13 | %ADD12P,1.869504X8X22.500000*% 14 | %ADD13C,5.283200*% 15 | %ADD14C,1.524000*% 16 | %ADD15C,1.981200*% 17 | %ADD16C,4.394200*% 18 | 19 | 20 | D10* 21 | X521970Y566420D03* 22 | X496570Y566420D03* 23 | X471170Y566420D03* 24 | X445770Y566420D03* 25 | X420370Y566420D03* 26 | X394970Y566420D03* 27 | X369570Y566420D03* 28 | X344170Y566420D03* 29 | X318770Y566420D03* 30 | X293370Y566420D03* 31 | X267970Y566420D03* 32 | X242570Y566420D03* 33 | X217170Y566420D03* 34 | X191770Y566420D03* 35 | X521970Y490220D03* 36 | X496570Y490220D03* 37 | X471170Y490220D03* 38 | X445770Y490220D03* 39 | X420370Y490220D03* 40 | X394970Y490220D03* 41 | X369570Y490220D03* 42 | X344170Y490220D03* 43 | X318770Y490220D03* 44 | X293370Y490220D03* 45 | X267970Y490220D03* 46 | X242570Y490220D03* 47 | X217170Y490220D03* 48 | D11* 49 | X191770Y490220D03* 50 | D12* 51 | X189992Y711200D03* 52 | X217678Y711200D03* 53 | X245110Y711200D03* 54 | X272796Y711200D03* 55 | X300482Y711200D03* 56 | X327914Y711200D03* 57 | X355600Y711200D03* 58 | X383286Y711200D03* 59 | X410718Y711200D03* 60 | X438404Y711200D03* 61 | X466090Y711200D03* 62 | X493522Y711200D03* 63 | X521208Y711200D03* 64 | X203708Y736600D03* 65 | X231394Y736600D03* 66 | X259080Y736600D03* 67 | X286512Y736600D03* 68 | X314198Y736600D03* 69 | X341884Y736600D03* 70 | X369316Y736600D03* 71 | X397002Y736600D03* 72 | X424688Y736600D03* 73 | X452120Y736600D03* 74 | X479806Y736600D03* 75 | X507492Y736600D03* 76 | D13* 77 | X120396Y723900D03* 78 | X590804Y723900D03* 79 | D14* 80 | X226060Y677164D02* 81 | X226060Y663956D01* 82 | X251460Y663956D02* 83 | X251460Y677164D01* 84 | X276860Y677164D02* 85 | X276860Y663956D01* 86 | X302260Y663956D02* 87 | X302260Y677164D01* 88 | X327660Y677164D02* 89 | X327660Y663956D01* 90 | X353060Y663956D02* 91 | X353060Y677164D01* 92 | X378460Y677164D02* 93 | X378460Y663956D01* 94 | X403860Y663956D02* 95 | X403860Y677164D01* 96 | X429260Y677164D02* 97 | X429260Y663956D01* 98 | X454660Y663956D02* 99 | X454660Y677164D01* 100 | X480060Y677164D02* 101 | X480060Y663956D01* 102 | D15* 103 | X246380Y627380D03* 104 | X271780Y627380D03* 105 | X297180Y627380D03* 106 | X322580Y627380D03* 107 | X347980Y627380D03* 108 | X373380Y627380D03* 109 | X398780Y627380D03* 110 | X424180Y627380D03* 111 | X449580Y627380D03* 112 | X474980Y627380D03* 113 | X500380Y627380D03* 114 | X525780Y627380D03* 115 | X246380Y424180D03* 116 | X271780Y424180D03* 117 | X297180Y424180D03* 118 | X322580Y424180D03* 119 | X347980Y424180D03* 120 | X373380Y424180D03* 121 | X398780Y424180D03* 122 | X424180Y424180D03* 123 | X449580Y424180D03* 124 | X474980Y424180D03* 125 | X500380Y424180D03* 126 | X525780Y424180D03* 127 | X220980Y424180D03* 128 | X195580Y424180D03* 129 | X170180Y424180D03* 130 | X144780Y424180D03* 131 | D16* 132 | X544830Y669290D03* 133 | X165100Y669290D03* 134 | M02* 135 | -------------------------------------------------------------------------------- /pcb/gerber/solderpaste_bottom.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INSolderpaste Bottom*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | 12 | 13 | M02* 14 | -------------------------------------------------------------------------------- /pcb/gerber/solderpaste_top.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INSolderpaste Top*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | 12 | 13 | M02* 14 | -------------------------------------------------------------------------------- /release/flashloader.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcs/WiFiStation/4e81091cba290e6e66405a127a409bf406a7e074/release/flashloader.bin -------------------------------------------------------------------------------- /release/version.txt: -------------------------------------------------------------------------------- 1 | 0.9.2 2 | 436768 3 | 3a0d11f62686355df376141464551d25 4 | https://raw.githubusercontent.com/jcs/WiFiStation/main/release/wifistation.bin 5 | -------------------------------------------------------------------------------- /release/wifistation.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcs/WiFiStation/4e81091cba290e6e66405a127a409bf406a7e074/release/wifistation.bin -------------------------------------------------------------------------------- /release/wsloader.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcs/WiFiStation/4e81091cba290e6e66405a127a409bf406a7e074/release/wsloader.bin --------------------------------------------------------------------------------