├── photos ├── Defender.gif ├── barbarian.gif ├── gameover.gif ├── mariobad.gif ├── nba-jam.jpg ├── robocop.jpg ├── insertcoin.gif ├── donkeykong03.gif ├── mortal combat.jpg ├── street-fighter.jpg ├── Installed Hardware.JPG └── Pwr & interconnect.jpg ├── FilenameFunctions.h ├── zmodem_config.h ├── zmodem_fixes.h ├── zmodem_zm.h ├── FilenameFunctions.cpp ├── GifDecoder.h ├── info.h ├── LzwDecoder_Impl.h ├── zmodem.h ├── README.md ├── zmodem_crc16.cpp ├── teensy2dmd.ino ├── zmodem_sz.cpp ├── zmodem_zm.cpp ├── GifDecoder_Impl.h └── zmodem_rz.cpp /photos/Defender.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gi1mic/teensy2dmd/HEAD/photos/Defender.gif -------------------------------------------------------------------------------- /photos/barbarian.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gi1mic/teensy2dmd/HEAD/photos/barbarian.gif -------------------------------------------------------------------------------- /photos/gameover.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gi1mic/teensy2dmd/HEAD/photos/gameover.gif -------------------------------------------------------------------------------- /photos/mariobad.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gi1mic/teensy2dmd/HEAD/photos/mariobad.gif -------------------------------------------------------------------------------- /photos/nba-jam.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gi1mic/teensy2dmd/HEAD/photos/nba-jam.jpg -------------------------------------------------------------------------------- /photos/robocop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gi1mic/teensy2dmd/HEAD/photos/robocop.jpg -------------------------------------------------------------------------------- /photos/insertcoin.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gi1mic/teensy2dmd/HEAD/photos/insertcoin.gif -------------------------------------------------------------------------------- /photos/donkeykong03.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gi1mic/teensy2dmd/HEAD/photos/donkeykong03.gif -------------------------------------------------------------------------------- /photos/mortal combat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gi1mic/teensy2dmd/HEAD/photos/mortal combat.jpg -------------------------------------------------------------------------------- /photos/street-fighter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gi1mic/teensy2dmd/HEAD/photos/street-fighter.jpg -------------------------------------------------------------------------------- /photos/Installed Hardware.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gi1mic/teensy2dmd/HEAD/photos/Installed Hardware.JPG -------------------------------------------------------------------------------- /photos/Pwr & interconnect.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gi1mic/teensy2dmd/HEAD/photos/Pwr & interconnect.jpg -------------------------------------------------------------------------------- /FilenameFunctions.h: -------------------------------------------------------------------------------- 1 | #ifndef FILENAME_FUNCTIONS_H 2 | #define FILENAME_FUNCTIONS_H 3 | 4 | int enumerateGIFFiles(const char *directoryName, boolean displayFilenames); 5 | void getGIFFilenameByIndex(const char *directoryName, int index, char *pnBuffer); 6 | int openGifFilenameByIndex(const char *directoryName, int index); 7 | int openGifFilenameByFilename(const char *pathname); 8 | int initSdCard(int chipSelectPin); 9 | 10 | 11 | bool fileSeekCallback(unsigned long position); 12 | unsigned long filePositionCallback(void); 13 | int fileReadCallback(void); 14 | int fileReadBlockCallback(void * buffer, int numberOfBytes); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /zmodem_config.h: -------------------------------------------------------------------------------- 1 | #ifndef ZMODEM_CONFIG_H 2 | #define ZMODEM_CONFIG_H 3 | 4 | #define Progname F("Teensy2DMD V1.0") 5 | 6 | // Dylan (monte_carlo_ecm, bitflipper, etc.) - The SparkFun MP3 shield (which contains an SDCard) 7 | // doesn't operate properly with the SDFat library (SPI related?) unless the MP3 library is 8 | // initialized as well. If you are using a SparkFun MP3 shield as your SDCard interface, 9 | // the following macro must be defined. Otherwise, comment it out. 10 | 11 | // Serial output for debugging info 12 | #define DSERIAL Serial 13 | 14 | // The Serial port for the Zmodem connection 15 | // must not be the same as DSERIAL unless all 16 | // debugging output to DSERIAL is removed 17 | //#define ZSERIAL Serial3 18 | #define ZSERIAL Serial 19 | 20 | #ifdef TEENSYDUINO 21 | #ifndef SERIAL_TX_BUFFER_SIZE 22 | #define SERIAL_TX_BUFFER_SIZE 64 23 | #endif 24 | #endif 25 | 26 | // Dylan (monte_carlo_ecm, bitflipper, etc.) - Adjust the baud rate to suit your board and needs 27 | #define ZMODEM_SPEED 57600 28 | 29 | // Dylan (monte_carlo_ecm, bitflipper, etc.) - For smaller boards (32K flash, 2K RAM) it may only 30 | // be possible to have only one or some of the following 3 features enabled at a time: 1) File manager 31 | // commands (DEL, MD, RD, etc.), 2) SZ (Send ZModem) or 3) RZ (Receive ZModem). Large boards 32 | // like the Arduino Mega can handle all 3 features in a single sketch easily, but for smaller boards like 33 | // Uno or Nano, it's very tight. It seems to work okay, but if you don't need the file manager commands, 34 | // or one of send or receive, comment out the associated macro and it'll slim the sketch down some. 35 | 36 | // Uncomment the following macro to build a version with file manipulation commands. 37 | 38 | #define ARDUINO_SMALL_MEMORY_INCLUDE_FILE_MGR 39 | 40 | // Uncomment the following macro to build a version with SZ enabled. 41 | 42 | #define ARDUINO_SMALL_MEMORY_INCLUDE_SZ 43 | 44 | // Uncomment the following macro to build a version with RZ enabled 45 | 46 | #define ARDUINO_SMALL_MEMORY_INCLUDE_RZ 47 | 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /zmodem_fixes.h: -------------------------------------------------------------------------------- 1 | /* 2 | Keep everything for ANSI prototypes. 3 | From: http://stackoverflow.com/questions/2607853/why-prototype-is-used-header-files 4 | */ 5 | 6 | #ifndef ZMODEM_FIXES_H 7 | #define ZMODEM_FIXES_H 8 | 9 | 10 | //////////////////////////////////////////////////////// 11 | 12 | 13 | #define _PROTOTYPE(function, params) function params 14 | 15 | #include 16 | 17 | extern SdFatSdioEX sd; 18 | 19 | #include 20 | 21 | // Dylan (monte_carlo_ecm, bitflipper, etc.) - changed serial read/write to macros to try to squeeze 22 | // out higher speed 23 | 24 | #define READCHECK 25 | #define TYPICAL_SERIAL_TIMEOUT 1200 26 | 27 | #define readline(timeout) ({ char _c; ZSERIAL.readBytes(&_c, 1) > 0 ? _c : TIMEOUT; }) 28 | int zdlread2(int); 29 | #define zdlread(void) ({ int _z; ((_z = readline(Rxtimeout)) & 0140) ? _z : zdlread2(_z); }) 30 | //#define sendline(_c) ZSERIAL.write(char(_c)) 31 | #define sendline(_c) ({ if (ZSERIAL.availableForWrite() > SERIAL_TX_BUFFER_SIZE / 2) ZSERIAL.flush(); ZSERIAL.write(char(_c)); }) 32 | #define zsendline(_z) ({ (_z & 0140 ) ? sendline(_z) : zsendline2(_z); }) 33 | 34 | void sendzrqinit(void); 35 | int wctxpn(const char *name); 36 | #define ARDUINO_RECV 37 | //int wcrx(void); 38 | int wcreceive(int argc, char **argp); 39 | 40 | extern int Filcnt; 41 | 42 | #define register int 43 | 44 | // If this is not defined the default is 1024. 45 | // It must be a power of 2 46 | 47 | #ifdef ARDUINO_SMALL_MEMORY 48 | #define TXBSIZE 1024 49 | #else 50 | #define TXBSIZE 1024 51 | #endif 52 | 53 | #define sleep(x) delay((x)*1000L) 54 | #define signal(x,y) 55 | 56 | // Handle the calls to exit - one has SS_NORMAL 57 | #define SS_NORMAL 0 58 | #define exit(n) 59 | 60 | // For now, evaluate it to zero so that it does not 61 | // enter the "if" statement's clause 62 | #define setjmp(...) 63 | 64 | #define printf(s, ... ) DSERIAL.println(s); 65 | #define fprintf(...) 66 | 67 | // fseek(in, Rxpos, 0) 68 | //#define fseek(fd, pos, rel) sdfile->seekSet(pos) 69 | //#define fclose(c) 70 | 71 | // ignore calls to mode() function in rbsb.cpp 72 | #define mode(a) 73 | 74 | #define sendbrk() 75 | 76 | //extern int Fromcu; 77 | void purgeline(void); 78 | 79 | #ifndef UNSL 80 | #define UNSL unsigned 81 | #endif 82 | 83 | void flushmo(void); 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /zmodem_zm.h: -------------------------------------------------------------------------------- 1 | #ifndef ZMODEM_ZM_h 2 | #define ZMODEM_ZM_H 3 | 4 | #define VERSION Progname 5 | 6 | extern char oneKbuf[1025]; 7 | 8 | //extern int Rxtimeout; /* Tenths of seconds to wait for something */ 9 | 10 | /* Globals used by ZMODEM functions */ 11 | extern uint8_t Rxframeind; /* ZBIN ZBIN32, or ZHEX type of frame received */ 12 | extern uint8_t Rxtype; /* Type of header received */ 13 | extern int Rxcount; /* Count of data bytes received */ 14 | extern char Rxhdr[4]; /* Received header */ 15 | extern char Txhdr[4]; /* Transmitted header */ 16 | extern long Rxpos; /* Received file position */ 17 | extern long Txpos; /* Transmitted file position */ 18 | extern int8_t Txfcs32; /* TRUE means send binary frames with 32 bit FCS */ 19 | extern int8_t Crc32t; /* Display flag indicating 32 bit CRC being sent */ 20 | extern int8_t Crc32; /* Display flag indicating 32 bit CRC being received */ 21 | //extern int Znulls; /* Number of nulls to send at beginning of ZDATA hdr */ 22 | extern char Attn[ZATTNLEN+1]; /* Attention string rx sends to tx on err */ 23 | 24 | extern int lastsent; /* Last char we sent */ 25 | extern uint8_t Not8bit; /* Seven bits seen on header */ 26 | 27 | //extern char *frametypes[]; 28 | 29 | extern uint32_t Baudrate; 30 | #define xsendline(c) sendline(c) 31 | //int readline(int timeout); 32 | 33 | #define OK 0 34 | #define FALSE 0 35 | #define TRUE 1 36 | #undef ERROR 37 | #define ERROR (-1) 38 | 39 | #ifndef ARDUINO 40 | #define zperr(a, ... ) 41 | #else 42 | /* 43 | #define WHERESTR "[FILE : %s, FUNC : %s, LINE : %d]: " 44 | #define WHEREARG __FILE__,__func__,__LINE__ 45 | #define DEBUG(...) {char s[256]; sprintf(s, __VA_ARGS__); DSERIAL.println(s);} 46 | #define zperr(_fmt, ...) DEBUG(WHERESTR _fmt, WHEREARG,__VA_ARGS__) 47 | */ 48 | //#define zperr(...) {char s[256]; sprintf(s, __VA_ARGS__); DSERIAL.println(s);} 49 | #define zperr(...) 50 | #endif 51 | 52 | void bttyout(int c); 53 | 54 | #define Zmodem 1 /* ZMODEM protocol requested */ 55 | extern uint8_t Verbose; 56 | extern char zconv; /* ZMODEM file conversion request */ 57 | extern char zmanag; /* ZMODEM file management request */ 58 | extern char ztrans; /* ZMODEM file transport request */ 59 | extern uint8_t Zctlesc; /* Encode control characters */ 60 | //extern int Zrwindow; /* RX window size (controls garbage count) */ 61 | //extern int Nozmodem; 62 | #define Nozmodem 0 63 | #define Lzmanag 0 64 | //extern int Restricted; 65 | //extern int Quiet; 66 | #define Quiet 0 67 | extern uint8_t Eofseen; 68 | 69 | extern uint8_t firstsec; 70 | extern char Lastrx; 71 | extern char Crcflg; 72 | extern uint8_t errors; 73 | // This is declared in the main sketch .ino 74 | //extern char *Progname; 75 | #endif 76 | -------------------------------------------------------------------------------- /FilenameFunctions.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Animated GIFs Display Code for SmartMatrix and 32x32 RGB LED Panels 3 | * 4 | * This file contains code to enumerate and select animated GIF files by name 5 | * 6 | * Written by: Craig A. Lindley 7 | */ 8 | 9 | #if defined (ARDUINO) 10 | #include "zmodem_config.h" 11 | #include 12 | extern SdFatSdioEX sd; 13 | #endif 14 | 15 | File file; 16 | 17 | char filename[40]; 18 | int numberOfFiles; 19 | 20 | bool fileSeekCallback(unsigned long position) 21 | { 22 | return file.seek(position); 23 | } 24 | 25 | unsigned long filePositionCallback(void) 26 | { 27 | return file.position(); 28 | } 29 | 30 | int fileReadCallback(void) 31 | { 32 | return file.read(); 33 | } 34 | 35 | int fileReadBlockCallback(void *buffer, int numberOfBytes) 36 | { 37 | return file.read((uint8_t *)buffer, numberOfBytes); 38 | } 39 | 40 | bool isAnimationFile(const char filename []) 41 | { 42 | String filenameString(filename); 43 | 44 | DSERIAL.print(filenameString); 45 | DSERIAL.flush(); 46 | 47 | if ((filenameString[0] == '_') || (filenameString[0] == '~') || (filenameString[0] == '.')) 48 | { 49 | DSERIAL.println(" ignoring: leading _/~/. character"); 50 | DSERIAL.flush(); 51 | return false; 52 | } 53 | 54 | filenameString.toUpperCase(); 55 | if (filenameString.endsWith(".GIF") != 1) 56 | { 57 | DSERIAL.println(" ignoring: doesn't end of .GIF"); 58 | DSERIAL.flush(); 59 | return false; 60 | } 61 | 62 | DSERIAL.println(); 63 | 64 | return true; 65 | } 66 | 67 | // Enumerate and possibly display the animated GIF filenames in GIFS directory 68 | int enumerateGIFFiles(const char *directoryName, bool displayFilenames) 69 | { 70 | numberOfFiles = 0; 71 | 72 | File directory = sd.open(directoryName); 73 | if (!directory) 74 | { 75 | return -1; 76 | } 77 | 78 | File file = directory.openNextFile(); 79 | while (file) 80 | { 81 | file.getName((const char *)filename, 40); 82 | if (isAnimationFile(filename)) 83 | { 84 | numberOfFiles++; 85 | if (displayFilenames == true) 86 | { 87 | file.getName((const char *)filename, 40); 88 | DSERIAL.println(filename); 89 | } 90 | } 91 | file.close(); 92 | file = directory.openNextFile(); 93 | } 94 | 95 | file.close(); 96 | directory.close(); 97 | 98 | return numberOfFiles; 99 | } 100 | 101 | // Get the full path/filename of the GIF file with specified index 102 | void getGIFFilenameByIndex(const char *directoryName, int index, char *pnBuffer) 103 | { 104 | // Make sure index is in range 105 | if ((index < 0) || (index >= numberOfFiles)) 106 | return; 107 | 108 | File directory = sd.open(directoryName); 109 | if (!directory) 110 | return; 111 | 112 | File file = directory.openNextFile(); 113 | while (file && (index >= 0)) 114 | { 115 | file.getName((const char *)pnBuffer, 40); 116 | 117 | if (isAnimationFile(pnBuffer)) 118 | { 119 | index--; 120 | } 121 | 122 | file.close(); 123 | file = directory.openNextFile(); 124 | } 125 | 126 | file.close(); 127 | directory.close(); 128 | } 129 | 130 | 131 | int openGifFilenameByIndex(const char *directoryName, int index) 132 | { 133 | char pathname[40]; 134 | 135 | getGIFFilenameByIndex(directoryName, index, pathname); 136 | 137 | DSERIAL.print("Pathname: "); 138 | DSERIAL.println(pathname); 139 | DSERIAL.flush(); 140 | 141 | if(file) 142 | file.close(); 143 | 144 | // Attempt to open the file for reading 145 | file = sd.open(pathname); 146 | if (!file) 147 | { 148 | DSERIAL.println("Error opening GIF file"); 149 | DSERIAL.flush(); 150 | return -1; 151 | } 152 | return 0; 153 | } 154 | 155 | 156 | int openGifFilenameByFilename(const char *pathname) 157 | { 158 | if(file) 159 | file.close(); 160 | 161 | // Attempt to open the file for reading 162 | file = sd.open(pathname); 163 | if (!file) 164 | { 165 | DSERIAL.print("Error opening GIF file: "); 166 | DSERIAL.println(pathname); 167 | DSERIAL.flush(); 168 | return -1; 169 | } 170 | 171 | DSERIAL.print("file: "); 172 | DSERIAL.println(pathname); 173 | DSERIAL.println(">"); 174 | DSERIAL.flush(); 175 | return 0; 176 | } 177 | -------------------------------------------------------------------------------- /GifDecoder.h: -------------------------------------------------------------------------------- 1 | #ifndef _GIFDECODER_H_ 2 | #define _GIFDECODER_H_ 3 | 4 | #include 5 | 6 | typedef void (*callback)(void); 7 | typedef void (*pixel_callback)(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue); 8 | typedef void* (*get_buffer_callback)(void); 9 | 10 | typedef bool (*file_seek_callback)(unsigned long position); 11 | typedef unsigned long (*file_position_callback)(void); 12 | typedef int (*file_read_callback)(void); 13 | typedef int (*file_read_block_callback)(void * buffer, int numberOfBytes); 14 | 15 | typedef struct rgb_24 { 16 | uint8_t red; 17 | uint8_t green; 18 | uint8_t blue; 19 | } rgb_24; 20 | 21 | // LZW constants 22 | // NOTE: LZW_MAXBITS should be set to 10 or 11 for small displays, 12 for large displays 23 | // all 32x32-pixel GIFs tested work with 11, most work with 10 24 | // LZW_MAXBITS = 12 will support all GIFs, but takes 16kB RAM 25 | #define LZW_SIZTABLE (1 << lzwMaxBits) 26 | 27 | template 28 | class GifDecoder { 29 | public: 30 | int startDecoding(void); 31 | int decodeFrame(void); 32 | 33 | void setScreenClearCallback(callback f); 34 | void setUpdateScreenCallback(callback f); 35 | void setDrawPixelCallback(pixel_callback f); 36 | void setStartDrawingCallback(callback f); 37 | 38 | void setFileSeekCallback(file_seek_callback f); 39 | void setFilePositionCallback(file_position_callback f); 40 | void setFileReadCallback(file_read_callback f); 41 | void setFileReadBlockCallback(file_read_block_callback f); 42 | 43 | private: 44 | void parseTableBasedImage(void); 45 | void decompressAndDisplayFrame(unsigned long filePositionAfter); 46 | int parseData(void); 47 | int parseGIFFileTerminator(void); 48 | void parseCommentExtension(void); 49 | void parseApplicationExtension(void); 50 | void parseGraphicControlExtension(void); 51 | void parsePlainTextExtension(void); 52 | void parseGlobalColorTable(void); 53 | void parseLogicalScreenDescriptor(void); 54 | bool parseGifHeader(void); 55 | void copyImageDataRect(uint8_t *dst, uint8_t *src, int x, int y, int width, int height); 56 | void fillImageData(uint8_t colorIndex); 57 | void fillImageDataRect(uint8_t colorIndex, int x, int y, int width, int height); 58 | int readIntoBuffer(void *buffer, int numberOfBytes); 59 | int readWord(void); 60 | void backUpStream(int n); 61 | int readByte(void); 62 | 63 | void lzw_decode_init(int csize); 64 | int lzw_decode(uint8_t *buf, int len, uint8_t *bufend); 65 | void lzw_setTempBuffer(uint8_t * tempBuffer); 66 | int lzw_get_code(void); 67 | 68 | // Logical screen descriptor attributes 69 | int lsdWidth; 70 | int lsdHeight; 71 | int lsdPackedField; 72 | int lsdAspectRatio; 73 | int lsdBackgroundIndex; 74 | 75 | // Table based image attributes 76 | int tbiImageX; 77 | int tbiImageY; 78 | int tbiWidth; 79 | int tbiHeight; 80 | int tbiPackedBits; 81 | bool tbiInterlaced; 82 | 83 | int frameDelay; 84 | int transparentColorIndex; 85 | int prevBackgroundIndex; 86 | int prevDisposalMethod; 87 | int disposalMethod; 88 | int lzwCodeSize; 89 | bool keyFrame; 90 | int rectX; 91 | int rectY; 92 | int rectWidth; 93 | int rectHeight; 94 | 95 | unsigned long nextFrameTime_ms; 96 | 97 | int colorCount; 98 | rgb_24 palette[256]; 99 | 100 | char tempBuffer[260]; 101 | 102 | // Buffer image data is decoded into 103 | uint8_t imageData[maxGifWidth * maxGifHeight]; 104 | 105 | // Backup image data buffer for saving portions of image disposal method == 3 106 | uint8_t imageDataBU[maxGifWidth * maxGifHeight]; 107 | 108 | callback screenClearCallback; 109 | callback updateScreenCallback; 110 | pixel_callback drawPixelCallback; 111 | callback startDrawingCallback; 112 | file_seek_callback fileSeekCallback; 113 | file_position_callback filePositionCallback; 114 | file_read_callback fileReadCallback; 115 | file_read_block_callback fileReadBlockCallback; 116 | 117 | // LZW variables 118 | int bbits; 119 | int bbuf; 120 | int cursize; // The current code size 121 | int curmask; 122 | int codesize; 123 | int clear_code; 124 | int end_code; 125 | int newcodes; // First available code 126 | int top_slot; // Highest code for current size 127 | int extra_slot; 128 | int slot; // Last read code 129 | int fc, oc; 130 | int bs; // Current buffer size for GIF 131 | int bcnt; 132 | uint8_t *sp; 133 | uint8_t * temp_buffer; 134 | 135 | uint8_t stack [LZW_SIZTABLE]; 136 | uint8_t suffix [LZW_SIZTABLE]; 137 | uint16_t prefix [LZW_SIZTABLE]; 138 | 139 | // Masks for 0 .. 16 bits 140 | unsigned int mask[17] = { 141 | 0x0000, 0x0001, 0x0003, 0x0007, 142 | 0x000F, 0x001F, 0x003F, 0x007F, 143 | 0x00FF, 0x01FF, 0x03FF, 0x07FF, 144 | 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 145 | 0xFFFF 146 | }; 147 | }; 148 | 149 | #include "GifDecoder_Impl.h" 150 | #include "LzwDecoder_Impl.h" 151 | 152 | #endif 153 | -------------------------------------------------------------------------------- /info.h: -------------------------------------------------------------------------------- 1 | // Arghhh. These three links have disappeared! 2 | // See this page for the original code: 3 | // http://www.raspberryginger.com/jbailey/minix/html/dir_acf1a49c3b8ff2cb9205e4a19757c0d6.html 4 | // From: http://www.raspberryginger.com/jbailey/minix/html/zm_8c-source.html 5 | // docs at: http://www.raspberryginger.com/jbailey/minix/html/zm_8c.html 6 | // The minix files here might be the same thing: 7 | // http://www.cise.ufl.edu/~cop4600/cgi-bin/lxr/http/source.cgi/commands/zmodem/ 8 | 9 | 10 | // It DOES NOT handle interruptions of the Tx or Rx lines so it 11 | // will NOT work in a hostile environment. 12 | 13 | /* 14 | Originally was an example by fat16lib of reading a directory 15 | and listing its files by directory entry number. 16 | See: http://forum.arduino.cc/index.php?topic=173562.0 17 | 18 | Heavily modified by Pete (El Supremo) to recursively list the files 19 | starting at a specified point in the directory structure and then 20 | use zmodem to transmit them to the PC via the ZSERIAL port 21 | 22 | Further heavy modifications by Dylan (monte_carlo_ecm, bitflipper, etc.) 23 | to create a user driven "file manager" of sorts. 24 | Many thanks to Pete (El Supremo) who got this started. Much work remained 25 | to get receive (rz) working, mostly due to the need for speed because of the 26 | very small (64 bytes) Serial buffer in the Arduino. 27 | 28 | I have tested this with an Arduino Mega 2560 R3 interfacing with Windows 10 29 | using Hyperterminal, Syncterm and TeraTerm. All of them seem to work, though 30 | their crash recovery (partial file transfer restart) behaviours vary. 31 | Syncterm kicks out a couple of non-fatal errors at the beginning of sending 32 | a file to the Arduino, but appears to always recover and complete the transfer. 33 | 34 | This sketch should work on any board with at least 30K of flash and 2K of RAM. 35 | Go to zmodem_config.h and disable some of the ARDUINO_SMALL_MEMORY_* macros 36 | for maximum peace of mind and stability if you don't need all the features 37 | (send, receive and file management). 38 | 39 | V2.1.2 40 | 2018-05-11 41 | - Fixes for Arduino IDE 1.8.5 42 | - Attempted to patch for use on Teensy 43 | 44 | V2.1 45 | 2015-03-06 46 | - Large scale code clean-up, reduction of variable sizes where they were 47 | unnecessarily large, sharing variables previously unshared between sz and 48 | rz, and creative use of the send/receive buffer allowed this sketch to 49 | BARELY fit and run with all features enabled on a board with 30K flash and 50 | 2K of RAM. Uno & Nano users - enjoy. 51 | - Some boards were unstable at baud rates above 9600. I tracked this back 52 | to overrunning the SERIAL_TX_BUFFER_SIZE to my surprise. Added a check 53 | if a flush() is required both in the help and directory listings, as well 54 | as the sendline() macro. 55 | 56 | V2.0 57 | 2015-02-23 58 | - Taken over by Dylan (monte_carlo_ecm, bitflipper, etc.) 59 | - Added Serial based user interface 60 | - Added support for SparkFun MP3 shield based SDCard (see zmodem_config.h) 61 | - Moved CRC tables to PROGMEM to lighten footprint on dynamic memory (zmodem_crc16.cpp) 62 | - Added ZRQINIT at start of sz. All terminal applications I tested didn't strictly need it, but it's 63 | super handy for getting the terminal application to auto start the download 64 | - Completed adaptation of rz to Arduino 65 | - Removed directory recursion for sz in favour of single file or entire current directory ("*") for sz 66 | - Optimized zdlread, readline, zsendline and sendline 67 | into macros for rz speed - still only up to 57600 baud 68 | - Enabled "crash recovery" for both sz and rz. Various terminal applications may respond differently 69 | to restarting partially completed transfers; experiment with yours to see how it behaves. This 70 | feature could be particularly useful if you have an ever growing log file and you just need to 71 | download the entries since your last download from your Arduino to your computer. 72 | 73 | V1.03 74 | 140913 75 | - remove extraneous code such as the entire main() function 76 | in sz and rz and anything dependent on the vax, etc. 77 | - moved purgeline, sendline, readline and bttyout from rz to zm 78 | so that the the zmodem_rz.cpp file is not required when compiling 79 | sz 80 | 81 | V1.02 82 | 140912 83 | - yup, sz transfer still works. 84 | 10 files -- 2853852 bytes 85 | Time = 265 secs 86 | 87 | V1.01 88 | 140912 89 | This was originally working on a T++2 and now works on T3 90 | - This works on a T3 using the RTC/GPS/uSD breadboard 91 | It sent multiple files - see info.h 92 | - both rz and sz sources compile together here but have not 93 | yet ensured that transmit still works. 94 | 95 | V1.00 96 | 130630 97 | - it compiles. It even times out. But it doesn't send anything 98 | to the PC - the TTYUSB LEDs don't blink at all 99 | - ARGHH. It does help to open the Serial1 port!! 100 | - but now it sends something to TTerm but TTerm must be answering 101 | with a NAK because they just repeat the same thing over 102 | and over again. 103 | 104 | V2.00 105 | 130702 106 | - IT SENT A FILE!!!! 107 | It should have sent two, but I'll take it! 108 | - tried sending 2012/09 at 115200 - it sent the first file (138kB!) 109 | but hangs when it starts on the second one. The file is created 110 | but is zero length. 111 | 112 | - THIS VERSION SENDS MULTIPLE FILES 113 | 114 | */ 115 | -------------------------------------------------------------------------------- /LzwDecoder_Impl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Animated GIFs Display Code for SmartMatrix and 32x32 RGB LED Panels 3 | * 4 | * This file contains code to decompress the LZW encoded animated GIF data 5 | * 6 | * Written by: Craig A. Lindley, Fabrice Bellard and Steven A. Bennett 7 | * See my book, "Practical Image Processing in C", John Wiley & Sons, Inc. 8 | * 9 | * Copyright (c) 2014 Craig A. Lindley 10 | * Minor modifications by Louis Beaudoin (pixelmatix) 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 13 | * this software and associated documentation files (the "Software"), to deal in 14 | * the Software without restriction, including without limitation the rights to 15 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 16 | * the Software, and to permit persons to whom the Software is furnished to do so, 17 | * subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in all 20 | * copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 24 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 25 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 26 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 27 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | */ 29 | 30 | #define LZWDEBUG 0 31 | 32 | #if defined (ARDUINO) 33 | #include 34 | #elif defined (SPARK) 35 | #include "application.h" 36 | #endif 37 | 38 | #include "GifDecoder.h" 39 | 40 | template 41 | void GifDecoder::lzw_setTempBuffer(uint8_t * tempBuffer) { 42 | temp_buffer = tempBuffer; 43 | } 44 | 45 | // Initialize LZW decoder 46 | // csize initial code size in bits 47 | // buf input data 48 | template 49 | void GifDecoder::lzw_decode_init (int csize) { 50 | 51 | // Initialize read buffer variables 52 | bbuf = 0; 53 | bbits = 0; 54 | bs = 0; 55 | bcnt = 0; 56 | 57 | // Initialize decoder variables 58 | codesize = csize; 59 | cursize = codesize + 1; 60 | curmask = mask[cursize]; 61 | top_slot = 1 << cursize; 62 | clear_code = 1 << codesize; 63 | end_code = clear_code + 1; 64 | slot = newcodes = clear_code + 2; 65 | oc = fc = -1; 66 | sp = stack; 67 | } 68 | 69 | // Get one code of given number of bits from stream 70 | template 71 | int GifDecoder::lzw_get_code() { 72 | 73 | while (bbits < cursize) { 74 | if (bcnt == bs) { 75 | // get number of bytes in next block 76 | readIntoBuffer(temp_buffer, 1); 77 | bs = temp_buffer[0]; 78 | readIntoBuffer(temp_buffer, bs); 79 | bcnt = 0; 80 | } 81 | bbuf |= temp_buffer[bcnt] << bbits; 82 | bbits += 8; 83 | bcnt++; 84 | } 85 | int c = bbuf; 86 | bbuf >>= cursize; 87 | bbits -= cursize; 88 | return c & curmask; 89 | } 90 | 91 | // Decode given number of bytes 92 | // buf 8 bit output buffer 93 | // len number of pixels to decode 94 | // returns the number of bytes decoded 95 | template 96 | int GifDecoder::lzw_decode(uint8_t *buf, int len, uint8_t *bufend) { 97 | int l, c, code; 98 | 99 | #if LZWDEBUG == 1 100 | unsigned char debugMessagePrinted = 0; 101 | #endif 102 | 103 | if (end_code < 0) { 104 | return 0; 105 | } 106 | l = len; 107 | 108 | for (;;) { 109 | while (sp > stack) { 110 | // load buf with data if we're still within bounds 111 | if(buf < bufend) { 112 | *buf++ = *(--sp); 113 | } else { 114 | // out of bounds, keep incrementing the pointers, but don't use the data 115 | #if LZWDEBUG == 1 116 | // only print this message once per call to lzw_decode 117 | if(buf == bufend) 118 | Serial.println("****** LZW imageData buffer overrun *******"); 119 | #endif 120 | } 121 | if ((--l) == 0) { 122 | return len; 123 | } 124 | } 125 | c = lzw_get_code(); 126 | if (c == end_code) { 127 | break; 128 | 129 | } 130 | else if (c == clear_code) { 131 | cursize = codesize + 1; 132 | curmask = mask[cursize]; 133 | slot = newcodes; 134 | top_slot = 1 << cursize; 135 | fc= oc= -1; 136 | 137 | } 138 | else { 139 | 140 | code = c; 141 | if ((code == slot) && (fc >= 0)) { 142 | *sp++ = fc; 143 | code = oc; 144 | } 145 | else if (code >= slot) { 146 | break; 147 | } 148 | while (code >= newcodes) { 149 | *sp++ = suffix[code]; 150 | code = prefix[code]; 151 | } 152 | *sp++ = code; 153 | if ((slot < top_slot) && (oc >= 0)) { 154 | suffix[slot] = code; 155 | prefix[slot++] = oc; 156 | } 157 | fc = code; 158 | oc = c; 159 | if (slot >= top_slot) { 160 | if (cursize < lzwMaxBits) { 161 | top_slot <<= 1; 162 | curmask = mask[++cursize]; 163 | } else { 164 | #if LZWDEBUG == 1 165 | if(!debugMessagePrinted) { 166 | debugMessagePrinted = 1; 167 | Serial.println("****** cursize >= lzwMaxBits *******"); 168 | } 169 | #endif 170 | } 171 | 172 | } 173 | } 174 | } 175 | end_code = -1; 176 | return len - l; 177 | } 178 | -------------------------------------------------------------------------------- /zmodem.h: -------------------------------------------------------------------------------- 1 | // From: http://www.raspberryginger.com/jbailey/minix/html/zmodem_8h-source.html 2 | #ifndef ZMODEM_H 3 | #define ZMODEM_H 4 | #include "zmodem_config.h" 5 | #include "zmodem_fixes.h" 6 | /* 7 | * Z M O D E M . H Manifest constants for ZMODEM 8 | * application to application file transfer protocol 9 | * 05-23-87 Chuck Forsberg Omen Technology Inc 10 | */ 11 | #define ZPAD '*' /* 052 Padding character begins frames */ 12 | #define ZDLE 030 /* Ctrl-X Zmodem escape - `ala BISYNC DLE */ 13 | #define ZDLEE (ZDLE^0100) /* Escaped ZDLE as transmitted */ 14 | #define ZBIN 'A' /* Binary frame indicator */ 15 | #define ZHEX 'B' /* HEX frame indicator */ 16 | #define ZBIN32 'C' /* Binary frame with 32 bit FCS */ 17 | 18 | /* Frame types (see array "frametypes" in zm.c) */ 19 | #define ZRQINIT 0 /* Request receive init */ 20 | #define ZRINIT 1 /* Receive init */ 21 | #define ZSINIT 2 /* Send init sequence (optional) */ 22 | #define ZACK 3 /* ACK to above */ 23 | #define ZFILE 4 /* File name from sender */ 24 | #define ZSKIP 5 /* To sender: skip this file */ 25 | #define ZNAK 6 /* Last packet was garbled */ 26 | #define ZABORT 7 /* Abort batch transfers */ 27 | #define ZFIN 8 /* Finish session */ 28 | #define ZRPOS 9 /* Resume data trans at this position */ 29 | #define ZDATA 10 /* Data packet(s) follow */ 30 | #define ZEOF 11 /* End of file */ 31 | #define ZFERR 12 /* Fatal Read or Write error Detected */ 32 | #define ZCRC 13 /* Request for file CRC and response */ 33 | #define ZCHALLENGE 14 /* Receiver's Challenge */ 34 | #define ZCOMPL 15 /* Request is complete */ 35 | #define ZCAN 16 /* Other end canned session with CAN*5 */ 36 | #define ZFREECNT 17 /* Request for free bytes on filesystem */ 37 | #define ZCOMMAND 18 /* Command from sending program */ 38 | #define ZSTDERR 19 /* Output to standard error, data follows */ 39 | 40 | /* ZDLE sequences */ 41 | #define ZCRCE 'h' /* CRC next, frame ends, header packet follows */ 42 | #define ZCRCG 'i' /* CRC next, frame continues nonstop */ 43 | #define ZCRCQ 'j' /* CRC next, frame continues, ZACK expected */ 44 | #define ZCRCW 'k' /* CRC next, ZACK expected, end of frame */ 45 | #define ZRUB0 'l' /* Translate to rubout 0177 */ 46 | #define ZRUB1 'm' /* Translate to rubout 0377 */ 47 | 48 | /* zdlread return values (internal) */ 49 | /* -1 is general error, -2 is timeout */ 50 | #define GOTOR 0400 51 | #define GOTCRCE (ZCRCE|GOTOR) /* ZDLE-ZCRCE received */ 52 | #define GOTCRCG (ZCRCG|GOTOR) /* ZDLE-ZCRCG received */ 53 | #define GOTCRCQ (ZCRCQ|GOTOR) /* ZDLE-ZCRCQ received */ 54 | #define GOTCRCW (ZCRCW|GOTOR) /* ZDLE-ZCRCW received */ 55 | #define GOTCAN (GOTOR|030) /* CAN*5 seen */ 56 | 57 | /* Byte positions within header array */ 58 | #define ZF0 3 /* First flags byte */ 59 | #define ZF1 2 60 | #define ZF2 1 61 | #define ZF3 0 62 | #define ZP0 0 /* Low order 8 bits of position */ 63 | #define ZP1 1 64 | #define ZP2 2 65 | #define ZP3 3 /* High order 8 bits of file position */ 66 | 67 | /* Bit Masks for ZRINIT flags byte ZF0 */ 68 | #define CANFDX 01 /* Rx can send and receive true FDX */ 69 | #define CANOVIO 02 /* Rx can receive data during disk I/O */ 70 | #define CANBRK 04 /* Rx can send a break signal */ 71 | #define CANCRY 010 /* Receiver can decrypt */ 72 | #define CANLZW 020 /* Receiver can uncompress */ 73 | #define CANFC32 040 /* Receiver can use 32 bit Frame Check */ 74 | #define ESCCTL 0100 /* Receiver expects ctl chars to be escaped */ 75 | #define ESC8 0200 /* Receiver expects 8th bit to be escaped */ 76 | 77 | /* Parameters for ZSINIT frame */ 78 | //#define ZATTNLEN 32 /* Max length of attention string */ 79 | #define ZATTNLEN 4 /* Need to take back as many bytes as possible, hopefully no one really sends a lengthy ATTN */ 80 | 81 | /* Bit Masks for ZSINIT flags byte ZF0 */ 82 | #define TESCCTL 0100 /* Transmitter expects ctl chars to be escaped */ 83 | #define TESC8 0200 /* Transmitter expects 8th bit to be escaped */ 84 | 85 | /* Parameters for ZFILE frame */ 86 | /* Conversion options one of these in ZF0 */ 87 | #define ZCBIN 1 /* Binary transfer - inhibit conversion */ 88 | #define ZCNL 2 /* Convert NL to local end of line convention */ 89 | #define ZCRESUM 3 /* Resume interrupted file transfer */ 90 | /* Management include options, one of these ored in ZF1 */ 91 | #define ZMSKNOLOC 0200 /* Skip file if not present at rx */ 92 | /* Management options, one of these ored in ZF1 */ 93 | #define ZMMASK 037 /* Mask for the choices below */ 94 | #define ZMNEWL 1 /* Transfer if source newer or longer */ 95 | #define ZMCRC 2 /* Transfer if different file CRC or length */ 96 | #define ZMAPND 3 /* Append contents to existing file (if any) */ 97 | #define ZMCLOB 4 /* Replace existing file */ 98 | #define ZMNEW 5 /* Transfer if source newer */ 99 | /* Number 5 is alive ... */ 100 | #define ZMDIFF 6 /* Transfer if dates or lengths different */ 101 | #define ZMPROT 7 /* Protect destination file */ 102 | /* Transport options, one of these in ZF2 */ 103 | #define ZTLZW 1 /* Lempel-Ziv compression */ 104 | #define ZTCRYPT 2 /* Encryption */ 105 | #define ZTRLE 3 /* Run Length encoding */ 106 | /* Extended options for ZF3, bit encoded */ 107 | #define ZXSPARS 64 /* Encoding for sparse file operations */ 108 | 109 | /* Parameters for ZCOMMAND frame ZF0 (otherwise 0) */ 110 | #define ZCACK1 1 /* Acknowledge, then do command */ 111 | 112 | //#ifdef NOTDEF 113 | // Pete (El Supremo) - fix up extern int 114 | /* Globals used by ZMODEM functions */ 115 | extern uint8_t Rxframeind; /* ZBIN ZBIN32, or ZHEX type of frame received */ 116 | extern uint8_t Rxtype; /* Type of header received */ 117 | extern int Rxcount; /* Count of data bytes received */ 118 | //extern int Zrwindow; /* RX window size (controls garbage count) */ 119 | extern int Rxtimeout; /* Tenths of seconds to wait for something */ 120 | extern char Rxhdr[4]; /* Received header */ 121 | extern char Txhdr[4]; /* Transmitted header */ 122 | extern long Rxpos; /* Received file position */ 123 | extern long Txpos; /* Transmitted file position */ 124 | extern int8_t Txfcs32; /* TURE means send binary frames with 32 bit FCS */ 125 | extern int8_t Crc32t; /* Display flag indicating 32 bit CRC being sent */ 126 | extern int8_t Crc32; /* Display flag indicating 32 bit CRC being received */ 127 | //extern int Znulls; /* Number of nulls to send at beginning of ZDATA hdr */ 128 | extern char Attn[ZATTNLEN+1]; /* Attention string rx sends to tx on err */ 129 | //#endif 130 | 131 | /* crctab.c */ 132 | _PROTOTYPE(long UPDC32 , (int b , long c )); 133 | 134 | /* rbsb.c */ 135 | #ifndef ARDUINO 136 | _PROTOTYPE(void from_cu , (void)); 137 | _PROTOTYPE(void cucheck , (void)); 138 | _PROTOTYPE(int rdchk , (int f )); 139 | _PROTOTYPE(int rdchk , (int f )); 140 | _PROTOTYPE(void sendbrk , (void)); 141 | #endif 142 | /* zm.c */ 143 | 144 | _PROTOTYPE(void zsbhdr , (int type , char *hdr )); 145 | _PROTOTYPE(void zshhdr , (int type , char *hdr )); 146 | _PROTOTYPE(void zsdata , (char *buf , int length , int frameend )); 147 | _PROTOTYPE(int zrdata , (char *buf , int length )); 148 | _PROTOTYPE(int zgethdr , (char *hdr , int eflag )); 149 | _PROTOTYPE(int zrbhdr , (char *hdr )); 150 | _PROTOTYPE(int zrbhdr32 , (char *hdr )); 151 | _PROTOTYPE(int zrhhdr , (char *hdr )); 152 | _PROTOTYPE(void zputhex , (int c )); 153 | _PROTOTYPE(int zsendline2 , (int c )); 154 | _PROTOTYPE(int zgethex , (void)); 155 | _PROTOTYPE(int zgeth1 , (void)); 156 | //_PROTOTYPE(int zdlread , (void)); 157 | _PROTOTYPE(int noxrd7 , (void)); 158 | _PROTOTYPE(void stohdr , (long pos )); 159 | _PROTOTYPE(long rclhdr , (char *hdr )); 160 | 161 | /* rz.c sz.c */ 162 | #ifndef ARDUINO 163 | void vfile(); 164 | #else 165 | #define vfile(a, ... ) 166 | #endif 167 | 168 | _PROTOTYPE(void bibi , (int n )); 169 | _PROTOTYPE(int wcs , (const char *oname)); 170 | _PROTOTYPE(void saybibi, (void)); 171 | 172 | int wctxpn(char *name,SdFile *file); 173 | int wcrx(); 174 | 175 | /* Ward Christensen / CP/M parameters - Don't change these! */ 176 | #define ENQ 005 177 | #define CAN ('X'&037) 178 | #define XOFF ('s'&037) 179 | #define XON ('q'&037) 180 | #define SOH 1 181 | #define STX 2 182 | #define EOT 4 183 | #define ACK 6 184 | #define NAK 025 185 | #define CPMEOF 032 186 | #define WANTCRC 0103 /* send C not NAK to get crc not checksum */ 187 | #define WANTG 0107 /* Send G not NAK to get nonstop batch xmsn */ 188 | #define TIMEOUT (-2) 189 | #define RCDO (-3) 190 | #define Tx_RETRYMAX 10 191 | #define Rx_RETRYMAX 5 192 | 193 | #endif 194 | 195 | /* End of ZMODEM.H */ 196 | 197 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # teensy2dmd 2 | Teensy LED Smartmatrix display for an animated GIF Arcade Marquee or text display 3 | 4 | 5 | This is a project to create a low cost animated 128x32 LED display for the marquee of a full-size MAME Arcade cabinet. The provided code allows the Teensy to process a simple command line protocol to browse the contents of the Teensy SD card, upload and download files using the ZMODEM protocol, change directories, display text messages and display animated GIF's on the attached LED panels. Displayed GIF's should have a resolution of 132x64 to match the attached panels. 6 | 7 | 8 | # Background 9 | 10 | During the Christmas period I had planned to update my full-size upright Arcade cabinet with the latest version of MAME and a new GUI front-end. By accident I stumbled on Pixelcade, a LED marquee for displaying animated GIF's. It was integrated with MAME to display different graphics depending on what you were doing. Unfortunately they where in a different country and given it was Christmas there was no way to get one in a reasonable time period. Also they used a raspberrypi with a WEB based REST interface for control which I did not want to use. 11 | 12 | Lucky I keep a number of Teensy boards and LED panels to hand and a quick web search lead me to the Smartmatrix library. Unfortunately I did not have the smartmatrixv4 hardware but I was able to order it with next day delivery for ~£18 from a UK supplier. Starting with a Teensy3.2 and the SmartmatrixV4 hardware it only took a few minutes to get LED panels showing animated GIFS using the examples. To be honest it took me longer to find suitably formatted animated GIF's than it did to get the code running. 13 | 14 | The smartmatrix library example only shows random animated GIFs from a specific folder while I wanted to drive the display via serial commands. I also quickly discovered having to extract the SD card every time I wanted to add a file to it was a major pain-in-the-ass. It was also not practical to remove the SD card in my intended application as the board would be inaccessible once inside my arcade cabinet. I was also keen to drive my planned display via BASH or BAT scripts. 15 | 16 | I did look at using the Teensy as an MTP device, i.e. it would appear on a windows machine like a mobile phone device, but I could not get this to work. So then I had a look for serial transfer protocols. A few minutes searching and I found a ZMODEM implementation offering serial commands, file upload, download and directory listing of the SD card. A few hours later and I had the code hacked together with the Smartmatrix animated GIF example. 17 | 18 | Since then I have implemented a few changes and added a few new features such as switching to the SDFAT libs for long filename support, implementing commands to allow for overlay text display, changed it to display files from the current working directory and a random display function that uses the currently active directory. 19 | 20 | Since starting this project I discovered there are various similar devices called NAME-YOUR-PROCESSOR2dmd systems. Most based around the raspberry-pi. Some of these implement clocks using animated backgrounds which would be an easy feature to add to this code using the overlay text feature but its not something I need. 21 | 22 | 23 | While the code is configured for a 128x32 display it will work at 64x32 using a single panel and a #define change. I have also tested the code using a 256x32 pixel display created from four 64x32 panels. This requires changing the uint8_t defining "kMatrixWidth" to a uint16_t (the rest of the code seemed to work fine). 24 | 25 | Original sources: 26 | 27 | https://github.com/pixelmatix/SmartMatrix 28 | 29 | https://github.com/ecm-bitflipper/Arduino_ZModem 30 | 31 | 32 | The panels are not fitted but this short video should give an idea of the final intention https://youtu.be/MTyskXJbor0 33 | 34 | 35 | # Physical requirements 36 | 37 | 2 x 64x32 P5 Led panels 38 | 39 | 1 x 5V 3A (3A or larger, 5A would be better) mains power supply (to power the panels). 40 | 41 | 1 x Teensy3.6 (Smaller boards don't have enough RAM) 42 | 43 | 1 x SmartmatrixV4 interface 44 | 45 | 1 x micro SD card 46 | 47 | 48 | 49 | 50 | Photo of a static GIF displayed on the panels 51 | ![alt text](https://github.com/gi1mic/teensy2dmd/blob/master/photos/street-fighter.jpg " Example static GIF displayed on the panels") 52 | 53 | 54 | Simulated example animated GIF 55 | 56 | ![alt text](https://github.com/gi1mic/teensy2dmd/blob/master/photos/Defender.gif " Example animated GIF") 57 | 58 | ![alt text](https://github.com/gi1mic/teensy2dmd/blob/master/photos/barbarian.gif " Example animated GIF") 59 | 60 | ![alt text](https://github.com/gi1mic/teensy2dmd/blob/master/photos/donkeykong03.gif " Example animated GIF") 61 | 62 | ![alt text](https://github.com/gi1mic/teensy2dmd/blob/master/photos/gameover.gif " Example animated GIF") 63 | 64 | ![alt text](https://github.com/gi1mic/teensy2dmd/blob/master/photos/insertcoin.gif " Example animated GIF") 65 | 66 | ![alt text](https://github.com/gi1mic/teensy2dmd/blob/master/photos/mariobad.gif " Example animated GIF") 67 | 68 | 69 | # Implemented Commands (may change): 70 | 71 | For testing you can use the Arduino IDE serial monitor to communicate with the programmed board at 57600 baud. But I highly recommend using "Tetra Term" for windows for more advanced testing. This is a free terminal emulator which supports uploading and downloading files using the ZMODEM protocol. 72 | 73 | Once connected type "help" and you should see the following: 74 | 75 | 76 | Teensy2DMD V1.0 - Transfer rate: 57600 77 | 78 | Available Commands: 79 | 80 | HELP - Print this list of commands 81 | 82 | BRI - Sets the brightness 0 - 255 (default) 83 | 84 | DIR - List files in current working directory - alternate LS 85 | 86 | DIS - Display the gif in current dir 87 | 88 | PWD - Print current working directory 89 | 90 | CD - Change current working directory 91 | 92 | DEL file - Delete file - alternate RM 93 | 94 | MD dir - Create dir - alternate MKDIR 95 | 96 | RD dir - Delete dir - alternate RMDIR 97 | 98 | RND - Display random images - alternate RANDOM 99 | 100 | SZ file - Send file from Teensy SD to terminal (* = all files) 101 | 102 | RZ - Receive a file from terminal to Teensy SD(Hyperterminal sends this 103 | 104 | automatically when you select Transfer->Send File...) 105 | 106 | TXT - Define displayed text - alternate TEXT 107 | 108 | TM - Set text mode - alternate TMODE 109 | 110 | Options - bounceForward, bounceReverse, stopped, off, wrapForwardFromLeft 111 | 112 | TF - Set text font (default font3x5) - alternate TFONT 113 | 114 | Options - font3x5, font5x7, font6x10, font8x13, gohufont11, gohufont11b 115 | 116 | TL - Set text loop count (default 1) - alternate TLOOP 117 | 118 | TOL - Set text left start offset - alternate LEFTOFFSET 119 | 120 | TOT - Set text top offset - alternate TOPOFFSET 121 | 122 | 123 | # GIF's for your SD card 124 | As a starting point there are 600 excellent free 128x32 animations made by eLLuiGi available from http://www.neo-arcadia.com/forum/viewtopic.php?f=14&t=67065#p1233644 125 | He has a lot more that can be purchased for a small donation. 126 | 127 | You can also use Googles advanced search feature to find animated GIF's that match a specific resolution. 128 | 129 | # Building the Hardware 130 | Ok there is now building, this is basically plug and play. You simply plug the Teensy 3.6 into the SmartmatrixV4 card. Then plug the Smartmatrix card into the HUB75 input socket on the rear of the LED panel. Then connect the HUB75 output socket from the first panel to the input socket on the second panel using the ribbon cable which came with the panels. 131 | Finally power the both panels using a 5V 3A power supply using the power cable that came with the panels. It is fine if the power supply provides more amps, but it must be at 5V. 132 | 133 | Installed Teensy 3.6 and SmartMatrixV4 HW 134 | ![alt text](https://github.com/gi1mic/teensy2dmd/blob/master/photos/Installed%20Hardware.JPG " Installed Teensy 3.6 and SmartMatrixV4 HW") 135 | 136 | Power and HUB75 Interconnect 137 | ![alt text](https://github.com/gi1mic/teensy2dmd/blob/master/photos/Pwr%20%26%20interconnect.jpg " Power and HUB75 Interconnect") 138 | 139 | 140 | # Example script files 141 | For DOS and MS Windows you can create a simple BAT file containing the following. Of course you will need to change the com port, filename and directory in the following example to suit your own needs 142 | 143 | @echo off 144 | 145 | REM Change the following port for your hardware (you can manually use the dos "mode" command to find it) 146 | set PORT=com52 147 | 148 | REM configure the com port - you should not need to change this 149 | mode %PORT%: baud=57600 parity=n data=8 stop=1 xon=off 150 | 151 | REM Change into the directory "/arcade" on the micro SD card - again change for your needs 152 | echo cd /arcade > \\.\%PORT%: 153 | 154 | REM Display the file yoshi.gif from the current active directory again change for your needs 155 | echo dis yoshi.gif > \\.\%PORT%: 156 | 157 | 158 | For Linux you can create a Bash script something like (Untested) 159 | 160 | #!/bin/bash 161 | 162 | # Change to match your USB serial port (you may need to change the port permissions to gain access) 163 | TTY=/dev/ttyUSB0 164 | 165 | # Set the serial port baud rate 166 | stty -F "${TTY}" 57600 167 | 168 | # Change into the directory "/arcade" on the micro SD card - again change for your needs 169 | cat cd /arcade > "${TTY}" 170 | 171 | # Display the file yoshi.gif from the current active directory again change for your needs 172 | cat dis yoshi.gif > "${TTY}" 173 | 174 | # Notes 175 | The Teensy 3.6 is required due to the extra memory it provides. You can get away with a Teensy3.2 if you only using 176 | a single 64x32 panel but you will need to physically attach a SD card and modify the code for the correct chip select (CS). The Teensy4.0 is not compatible with the Smartmatrix library. 177 | 178 | -------------------------------------------------------------------------------- /zmodem_crc16.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | /* 5 | * Crc calculation stuff 6 | */ 7 | #ifndef ZMODEM_CRC16_CPP 8 | #define ZMODEM_CRC16_CPP 9 | /* crctab calculated by Mark G. Mendel, Network Systems Corporation */ 10 | // Dylan (monte_carlo_ecm, bitflipper, etc.) - Moved to PROGMEM 11 | PROGMEM static const unsigned short crctab[256] = { 12 | 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 13 | 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 14 | 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 15 | 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 16 | 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 17 | 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 18 | 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 19 | 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 20 | 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 21 | 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 22 | 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 23 | 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 24 | 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 25 | 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 26 | 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 27 | 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 28 | 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 29 | 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 30 | 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 31 | 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 32 | 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 33 | 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 34 | 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 35 | 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 36 | 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 37 | 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 38 | 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 39 | 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 40 | 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 41 | 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 42 | 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 43 | 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 44 | }; 45 | 46 | /* 47 | * updcrc macro derived from article Copyright (C) 1986 Stephen Satchell. 48 | * NOTE: First argument must be in range 0 to 255. 49 | * Second argument is referenced twice. 50 | * 51 | * Programmers may incorporate any or all code into their programs, 52 | * giving proper credit within the source. Publication of the 53 | * source routines is permitted so long as proper credit is given 54 | * to Stephen Satchell, Satchell Evaluations and Chuck Forsberg, 55 | * Omen Technology. 56 | */ 57 | // Pete (El Supremo) Can't parenthesize this in a way that gets rid of a compiler 58 | // warning on line 283 of zmodem_zm.cpp 59 | // Dylan (monte_carlo_ecm, bitflipper, etc.) - No warning in Arduino IDE 1.6.5 60 | 61 | #ifdef ARDUINO 62 | #define updcrc(cp, crc) ( ( (pgm_read_word(crctab + ((crc >> 8) & 255)) ^ (crc << 8) ) ^ cp)) 63 | #else 64 | #define updcrc(cp, crc) ( ( (crctab[((crc >> 8) & 255)] ^ (crc << 8) ) ^ cp)) 65 | #endif 66 | 67 | /* 68 | * Copyright (C) 1986 Gary S. Brown. You may use this program, or 69 | * code or tables extracted from it, as desired without restriction. 70 | */ 71 | 72 | /* First, the polynomial itself and its table of feedback terms. The */ 73 | /* polynomial is */ 74 | /* X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 */ 75 | /* Note that we take it "backwards" and put the highest-order term in */ 76 | /* the lowest-order bit. The X^32 term is "implied"; the LSB is the */ 77 | /* X^31 term, etc. The X^0 term (usually shown as "+1") results in */ 78 | /* the MSB being 1. */ 79 | 80 | /* Note that the usual hardware shift register implementation, which */ 81 | /* is what we're using (we're merely optimizing it by doing eight-bit */ 82 | /* chunks at a time) shifts bits into the lowest-order term. In our */ 83 | /* implementation, that means shifting towards the right. Why do we */ 84 | /* do it this way? Because the calculated CRC must be transmitted in */ 85 | /* order from highest-order term to lowest-order term. UARTs transmit */ 86 | /* characters in order from LSB to MSB. By storing the CRC this way, */ 87 | /* we hand it to the UART in the order low-byte to high-byte; the UART */ 88 | /* sends each low-bit to hight-bit; and the result is transmission bit */ 89 | /* by bit from highest- to lowest-order term without requiring any bit */ 90 | /* shuffling on our part. Reception works similarly. */ 91 | 92 | /* The feedback terms table consists of 256, 32-bit entries. Notes: */ 93 | /* */ 94 | /* The table can be generated at runtime if desired; code to do so */ 95 | /* is shown later. It might not be obvious, but the feedback */ 96 | /* terms simply represent the results of eight shift/xor opera- */ 97 | /* tions for all combinations of data and CRC register values. */ 98 | /* */ 99 | /* The values must be right-shifted by eight bits by the "updcrc" */ 100 | /* logic; the shift must be unsigned (bring in zeroes). On some */ 101 | /* hardware you could probably optimize the shift in assembler by */ 102 | /* using byte-swap instructions. */ 103 | 104 | // Pete (El_Supremo) add 'unsigned' 105 | // Dylan (monte_carlo_ecm, bitflipper, etc.) - Moved to PROGMEM 106 | PROGMEM static const unsigned long cr3tab[] = { /* CRC polynomial 0xedb88320 */ 107 | 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 108 | 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 109 | 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 110 | 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 111 | 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 112 | 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 113 | 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 114 | 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 115 | 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 116 | 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 117 | 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 118 | 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 119 | 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 120 | 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 121 | 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 122 | 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 123 | 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 124 | 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 125 | 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 126 | 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 127 | 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 128 | 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 129 | 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 130 | 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 131 | 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 132 | 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 133 | 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 134 | 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 135 | 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 136 | 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 137 | 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 138 | 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d 139 | }; 140 | 141 | #ifdef NFGM 142 | long 143 | UPDC32(b, c) 144 | long c; 145 | { 146 | return (pgm_read_dword(cr3tab + (((int)c ^ b) & 0xff)) ^ ((c >> 8) & 0x00FFFFFF)); 147 | // return (cr3tab[((int)c ^ b) & 0xff] ^ ((c >> 8) & 0x00FFFFFF)); 148 | } 149 | 150 | #else 151 | #ifdef ARDUINO 152 | #define UPDC32(b, c) (pgm_read_dword(cr3tab + (((int)c ^ b) & 0xff)) ^ ((c >> 8) & 0x00FFFFFF)) 153 | #else 154 | #define UPDC32(b, c) (cr3tab[((int)c ^ b) & 0xff] ^ ((c >> 8) & 0x00FFFFFF)) 155 | #endif 156 | 157 | #endif 158 | 159 | /* End of crctab.c */ 160 | #endif 161 | -------------------------------------------------------------------------------- /teensy2dmd.ino: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include "info.h" 3 | #include "zmodem_config.h" 4 | #include "zmodem.h" 5 | #include "zmodem_zm.h" 6 | 7 | /* 8 | * https://github.com/gi1mic/teensy2dmd 9 | * 10 | * 11 | * This program is a merger of the arduino zmodem implementation from 12 | * https://github.com/ecm-bitflipper/Arduino_ZModem and the teensey smartmatrix library 13 | * from https://github.com/pixelmatix/SmartMatrix 14 | * 15 | * It allows the display of animated gifs and text and is intended for use as a banner on an arcade machine. 16 | * 17 | * It has been modified to run on a Teensey 3.6 board and uses the smartmatrix V4 18 | * shield for interfacing to two 64x32 LED panels to create a 128x32 display using a HUB75 interface. 19 | * 20 | * https://www.adafruit.com/product/1902 21 | * https://www.pjrc.com/store/teensy36.html 22 | * 23 | * A SD card is used to hold the animated GIF's 24 | * 25 | * It has been tested with Tera Term 4.96 running on a PC. This allows the user to view the contents 26 | * of the SD card, change directories, upload/download files using the zmodem protocol and display GIF 27 | * files from the current active directory. 28 | * 29 | * You can find 600 free 128x32 animations made by eLLuiGi at http://www.neo-arcadia.com/forum/viewtopic.php?f=14&t=67065#p1233644 30 | * He can provide a few thousand more animations for a small donation. 31 | * 32 | * More information about the origional code is detailed in info.h (Please read) 33 | */ 34 | 35 | 36 | 37 | #include // comment out this line for if you're not using SmartLED Shield V4 hardware (this line needs to be before #include ) 38 | #include 39 | #include // From https://github.com/adafruit/SdFat 40 | #include "GifDecoder.h" 41 | #include "FilenameFunctions.h" 42 | 43 | SdFatSdioEX sd; 44 | 45 | #define DISPLAY_TIME_SECONDS 10 46 | #define error(s) sd.errorHalt(s) 47 | #define DSERIALprintln(_p) ({ DSERIALprint(_p); DSERIAL.write("\r\n"); }) 48 | #define fname (&oneKbuf[512]) 49 | #define dir ((dir_t *)&oneKbuf[256]) 50 | 51 | extern int Filesleft; 52 | extern long Totalleft; 53 | 54 | SdFile fout; 55 | 56 | int num_files; 57 | int defaultBrightness = 255; // range 0-255 58 | 59 | rgb24 tColour = {0xff, 0xff, 0xff}; 60 | ScrollMode tMode = wrapForward; // wrapForward, bounceForward, bounceReverse, stopped, off, wrapForwardFromLeft 61 | int tSpeed = 40; 62 | fontChoices tFont = font3x5; // font3x5, font5x7, font6x10, font8x13, gohufont11, gohufont11b 63 | char tText[50] = "Undefined text message"; 64 | int tLoopCount = 1; 65 | 66 | const rgb24 COLOR_BLACK = { 0, 0, 0 }; 67 | 68 | /* SmartMatrix configuration and memory allocation */ 69 | #define COLOR_DEPTH 24 // known working: 24, 48 - If the sketch uses type `rgb24` directly, COLOR_DEPTH must be 24 70 | #define refreshRate 90 71 | const uint8_t kMatrixWidth = 128; // known working: 32, 64, 96, 128 72 | const uint8_t kMatrixHeight = 32; // known working: 16, 32, 48, 64 73 | const uint8_t kRefreshDepth = 36; // known working: 24, 36, 48 74 | const uint8_t kDmaBufferRows = 2; // known working: 2-4 75 | 76 | const uint8_t kPanelType = SMARTMATRIX_HUB75_32ROW_MOD16SCAN; // use SMARTMATRIX_HUB75_16ROW_MOD8SCAN for common 16x32 panels, or use SMARTMATRIX_HUB75_64ROW_MOD32SCAN for common 64x64 panels 77 | const uint8_t kMatrixOptions = (SMARTMATRIX_OPTIONS_NONE); // see http://docs.pixelmatix.com/SmartMatrix for options 78 | const uint8_t kBackgroundLayerOptions = (SM_BACKGROUND_OPTIONS_NONE); 79 | const uint8_t kScrollingLayerOptions = (SM_SCROLLING_OPTIONS_NONE); 80 | 81 | SMARTMATRIX_ALLOCATE_BUFFERS(matrix, kMatrixWidth, kMatrixHeight, kRefreshDepth, kDmaBufferRows, kPanelType, kMatrixOptions); 82 | SMARTMATRIX_ALLOCATE_BACKGROUND_LAYER(backgroundLayer, kMatrixWidth, kMatrixHeight, COLOR_DEPTH, kBackgroundLayerOptions); 83 | SMARTMATRIX_ALLOCATE_SCROLLING_LAYER(scrollingLayer, kMatrixWidth, kMatrixHeight, COLOR_DEPTH, kScrollingLayerOptions); 84 | 85 | // template parameters are maxGifWidth, maxGifHeight, lzwMaxBits 86 | GifDecoder decoder; 87 | 88 | size_t DSERIALprint(const __FlashStringHelper *ifsh) 89 | { 90 | PGM_P p = reinterpret_cast(ifsh); 91 | size_t n = 0; 92 | while (1) 93 | { 94 | unsigned char c = pgm_read_byte(p++); 95 | if (c == 0) break; 96 | if (DSERIAL.availableForWrite() > SERIAL_TX_BUFFER_SIZE / 2) DSERIAL.flush(); 97 | if (DSERIAL.write(c)) n++; 98 | else break; 99 | } 100 | return n; 101 | } 102 | 103 | 104 | //------------------------------------------------ 105 | void screenClearCallback(void) 106 | { 107 | backgroundLayer.fillScreen({0, 0, 0}); 108 | } 109 | 110 | //------------------------------------------------ 111 | void updateScreenCallback(void) 112 | { 113 | backgroundLayer.swapBuffers(); 114 | } 115 | 116 | //------------------------------------------------ 117 | void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue) 118 | { 119 | backgroundLayer.drawPixel(x, y, {red, green, blue}); 120 | } 121 | 122 | 123 | //------------------------------------------------ 124 | void help(void) 125 | { 126 | DSERIALprint(Progname); 127 | DSERIALprint(F(" - Transfer rate: ")); DSERIAL.flush(); 128 | DSERIAL.println(ZMODEM_SPEED); DSERIAL.flush(); 129 | DSERIALprintln(F("Available Commands:")); DSERIAL.flush(); 130 | DSERIALprintln(F("HELP - Print this list of commands")); DSERIAL.flush(); 131 | DSERIALprintln(F("BRI - Sets the brightness 0 - 255 (default)")); DSERIAL.flush(); 132 | DSERIALprintln(F("DIR - List files in current working directory - alternate LS")); DSERIAL.flush(); 133 | DSERIALprintln(F("DIS - Display the gif in current working directory")); DSERIAL.flush(); 134 | DSERIALprintln(F("PWD - Print current working directory")); DSERIAL.flush(); 135 | DSERIALprintln(F("CD - Change current working directory")); DSERIAL.flush(); 136 | DSERIALprintln(F("DEL file - Delete file - alternate RM")); DSERIAL.flush(); 137 | DSERIALprintln(F("MD dir - Create dir - alternate MKDIR")); DSERIAL.flush(); 138 | DSERIALprintln(F("RD dir - Delete dir - alternate RMDIR")); DSERIAL.flush(); 139 | DSERIALprintln(F("RND - Display random images - alternate RANDOM")); DSERIAL.flush(); 140 | DSERIALprintln(F("SZ file - Send file from Arduino to terminal (* = all files)")); DSERIAL.flush(); 141 | DSERIALprintln(F("RZ - Receive a file from terminal to Arduino (Hyperterminal sends this")); DSERIAL.flush(); 142 | DSERIALprintln(F(" automatically when you select Transfer->Send File...)")); DSERIAL.flush(); 143 | DSERIALprintln(F("TXT - Define displayed text - alternate TEXT")); DSERIAL.flush(); 144 | DSERIALprintln(F("TM - Set text mode - alternate TMODE")); DSERIAL.flush(); 145 | DSERIALprintln(F(" Options - bounceForward, bounceReverse, stopped, off, wrapForwardFromLeft")); DSERIAL.flush(); 146 | DSERIALprintln(F("TF - Set text font (default font3x5) - alternate TFONT")); DSERIAL.flush(); 147 | DSERIALprintln(F(" Options - font3x5, font5x7, font6x10, font8x13, gohufont11, gohufont11b")); DSERIAL.flush(); 148 | DSERIALprintln(F("TL - Set text loop count (default 1) - alternate TLOOP")); DSERIAL.flush(); 149 | DSERIALprintln(F("TOL - Set text left start offset - alternate LEFTOFFSET")); DSERIAL.flush(); 150 | DSERIALprintln(F("TOT - Set text top offset - alternate TOPOFFSET")); DSERIAL.flush(); 151 | } 152 | 153 | 154 | //------------------------------------------------ 155 | //int asciiHexToInt(char c) { 156 | // int x = 0; 157 | // if (c >= "0" && c <= "9") x += c - "0"; 158 | // else if (c >= "A" && c <= "F") x += c - "A"; 159 | // return x; 160 | //} 161 | 162 | //------------------------------------------------ 163 | void setup() 164 | { 165 | // NOTE: The following line needs to be uncommented if DSERIAL and ZSERIAL are decoupled again for debugging 166 | // DSERIAL.begin(115200); 167 | 168 | ZSERIAL.begin(ZMODEM_SPEED); 169 | ZSERIAL.setTimeout(TYPICAL_SERIAL_TIMEOUT); 170 | delay(400); 171 | 172 | //Initialize the SdCard. 173 | if (!sd.begin()) sd.initErrorHalt(&DSERIAL); 174 | if (!sd.chdir("/", true)) sd.errorHalt(F("sd.chdir")); 175 | 176 | sd.vwd()->rewind(); 177 | 178 | decoder.setScreenClearCallback(screenClearCallback); 179 | decoder.setUpdateScreenCallback(updateScreenCallback); 180 | decoder.setDrawPixelCallback(drawPixelCallback); 181 | 182 | decoder.setFileSeekCallback(fileSeekCallback); 183 | decoder.setFilePositionCallback(filePositionCallback); 184 | decoder.setFileReadCallback(fileReadCallback); 185 | decoder.setFileReadBlockCallback(fileReadBlockCallback); 186 | 187 | // Init text 188 | scrollingLayer.setColor(tColour); 189 | scrollingLayer.setMode(tMode); 190 | scrollingLayer.setSpeed(tSpeed); 191 | scrollingLayer.setFont(tFont); 192 | 193 | // Initialize matrix 194 | matrix.addLayer(&backgroundLayer); 195 | matrix.addLayer(&scrollingLayer); 196 | matrix.setBrightness(defaultBrightness); 197 | matrix.setRefreshRate(refreshRate); 198 | matrix.begin(); 199 | 200 | // Clear screen 201 | backgroundLayer.fillScreen(COLOR_BLACK); 202 | backgroundLayer.swapBuffers(false); 203 | 204 | help(); 205 | } 206 | 207 | //------------------------------------------------ 208 | int count_files(int *file_count, long *byte_count) 209 | { 210 | *file_count = 0; 211 | *byte_count = 0; 212 | 213 | sd.vwd()->rewind(); 214 | 215 | while (sd.vwd()->readDir(dir) == sizeof(*dir)) 216 | { 217 | // read next directory entry in current working directory 218 | SdFile::dirName(dir, fname); // format file name 219 | 220 | // remember position in directory 221 | uint32_t pos = sd.vwd()->curPosition(); 222 | 223 | if (!fout.open(fname, O_READ)) error(F("file.open failed")); // open file 224 | else if (!sd.vwd()->seekSet(pos)) error(F("seekSet failed")); // restore root position 225 | else if (!fout.isDir()) 226 | { 227 | *file_count = *file_count + 1; 228 | *byte_count = *byte_count + fout.fileSize(); 229 | } 230 | fout.close(); 231 | } 232 | return 0; 233 | } 234 | 235 | 236 | //------------------------------------------------ 237 | void loop(void) 238 | { 239 | char *cmd = oneKbuf; 240 | char *param; 241 | 242 | static unsigned long futureTime; 243 | char curPath[40]; 244 | 245 | *cmd = 0; 246 | while (DSERIAL.available()) DSERIAL.read(); 247 | 248 | char c = 0; 249 | while (1) 250 | { 251 | if (DSERIAL.available() > 0) 252 | { 253 | c = DSERIAL.read(); 254 | if ((c == 8 or c == 127) && strlen(cmd) > 0) cmd[strlen(cmd) - 1] = 0; 255 | if (c == '\n' || c == '\r') break; 256 | DSERIAL.write(c); 257 | if (c != 8 && c != 127) strncat(cmd, &c, 1); 258 | } 259 | else 260 | { 261 | delay(20); 262 | } 263 | decoder.decodeFrame(); 264 | } 265 | 266 | param = strchr(cmd, 32); 267 | if (param > 0) 268 | { 269 | *param = 0; 270 | param = param + 1; 271 | } 272 | else 273 | { 274 | param = &cmd[strlen(cmd)]; 275 | } 276 | 277 | strupr(cmd); 278 | DSERIAL.println(); 279 | 280 | 281 | if (!strcmp_P(cmd, PSTR("HELP"))) 282 | { 283 | //------------------------------ 284 | help(); 285 | } 286 | else if (!strcmp_P(cmd, PSTR("DIR")) || !strcmp_P(cmd, PSTR("LS"))) 287 | { 288 | //------------------------------ 289 | char filename[40]; 290 | 291 | sd.vwd()->getName(fname, 13); 292 | snprintf(curPath, sizeof(curPath), "/%s/", fname); 293 | curPath[strlen(curPath)] = '\0'; 294 | 295 | DSERIALprint(F("Directory Listing for: ")); 296 | DSERIALprintln(F(curPath)); DSERIAL.flush(); 297 | File directory = sd.open(curPath); 298 | if (!directory) 299 | { 300 | DSERIALprint(F("Not a Directory")); DSERIAL.flush(); 301 | return -1; 302 | } 303 | 304 | File file = directory.openNextFile(); 305 | while (file) 306 | { 307 | file.getName((const char *)filename, 40); 308 | DSERIALprintln(F(filename)); DSERIAL.flush(); 309 | file.close(); 310 | file = directory.openNextFile(); 311 | } 312 | 313 | file.close(); 314 | directory.close(); 315 | 316 | DSERIALprintln(F("End of Directory")); DSERIAL.flush(); 317 | } 318 | else if (!strcmp_P(cmd, PSTR("BRI"))) { 319 | //------------------------------ 320 | DSERIALprint(F("Brightness: ")); 321 | DSERIAL.print(param); DSERIAL.flush(); 322 | defaultBrightness = atoi(param); 323 | matrix.setBrightness(defaultBrightness); 324 | 325 | } 326 | else if (!strcmp_P(cmd, PSTR("PWD"))) 327 | { 328 | //------------------------------ 329 | sd.vwd()->getName(fname, 13); 330 | DSERIALprint(F("Current working directory is ")); 331 | DSERIAL.println(fname); DSERIAL.flush(); 332 | 333 | } 334 | else if (!strcmp_P(cmd, PSTR("CD"))) 335 | { 336 | //------------------------------ 337 | if (!sd.chdir(param, true)) 338 | { 339 | DSERIALprint(F("Directory ")); 340 | DSERIAL.print(param); 341 | DSERIALprintln(F(" not found")); DSERIAL.flush(); 342 | } 343 | else 344 | { 345 | DSERIALprint(F("Current directory changed to ")); 346 | DSERIAL.println(param); DSERIAL.flush(); 347 | } 348 | } 349 | else if (!strcmp_P(cmd, PSTR("DIS"))) 350 | { 351 | //------------------------------ 352 | char filename[40]; 353 | 354 | sd.vwd()->getName(fname, 13); 355 | snprintf(filename, sizeof(filename), "/%s/%s", fname, param); 356 | filename[strlen(filename)] = '\0'; 357 | 358 | if (openGifFilenameByFilename((const char *) filename) > 0) 359 | { 360 | DSERIALprint(F("Failed to display ")); 361 | DSERIAL.println(param); DSERIAL.flush(); 362 | } 363 | else 364 | { 365 | DSERIALprint(F("Displaying ")); 366 | DSERIAL.println(param); DSERIAL.flush(); 367 | backgroundLayer.fillScreen(COLOR_BLACK); 368 | backgroundLayer.swapBuffers(); 369 | decoder.startDecoding(); 370 | 371 | scrollingLayer.start(tText, tLoopCount); 372 | } 373 | } 374 | else if (!strcmp_P(cmd, PSTR("TXT")) || !strcmp_P(cmd, PSTR("TEXT"))) 375 | { 376 | //------------------------------ 377 | DSERIALprint(F("Text: ")); 378 | DSERIAL.println(param); DSERIAL.flush(); 379 | 380 | scrollingLayer.start(param, tLoopCount); 381 | } 382 | else if (!strcmp_P(cmd, PSTR("TL")) || !strcmp_P(cmd, PSTR("TLOOP"))) 383 | { 384 | //------------------------------ 385 | DSERIALprint(F("Tloop: ")); 386 | DSERIAL.println(param); DSERIAL.flush(); 387 | 388 | tLoopCount = (atoi(param)); 389 | } 390 | else if (!strcmp_P(cmd, PSTR("TS")) || !strcmp_P(cmd, PSTR("TSPEED"))) 391 | { 392 | //------------------------------ 393 | DSERIALprint(F("Tspeed: ")); 394 | DSERIAL.println(param); DSERIAL.flush(); 395 | 396 | scrollingLayer.setSpeed(atoi(param)); 397 | 398 | } 399 | else if (!strcmp_P(cmd, PSTR("TOT")) || !strcmp_P(cmd, PSTR("TOPOFFSET"))) 400 | { 401 | //------------------------------ 402 | DSERIALprint(F("Top Offest: ")); 403 | DSERIAL.println(param); DSERIAL.flush(); 404 | 405 | if (atoi(param) > kMatrixHeight) { 406 | DSERIALprintln(F("Offset exceeds display height")); DSERIAL.flush(); 407 | } else 408 | scrollingLayer.setOffsetFromTop(atoi(param)); 409 | } 410 | else if (!strcmp_P(cmd, PSTR("TOL")) || !strcmp_P(cmd, PSTR("LEFTOFFSET"))) 411 | { 412 | //------------------------------ 413 | DSERIALprint(F("Left Start Offest: ")); 414 | DSERIAL.println(param); DSERIAL.flush(); 415 | 416 | if (atoi(param) > kMatrixWidth) { 417 | DSERIALprintln(F("Offset exceeds display width")); DSERIAL.flush(); 418 | } else 419 | scrollingLayer.setStartOffsetFromLeft(atoi(param)); 420 | } 421 | else if (!strcmp_P(cmd, PSTR("TM")) || !strcmp_P(cmd, PSTR("TMODE"))) 422 | { 423 | //------------------------------ 424 | DSERIALprint(F("tMode: ")); 425 | DSERIAL.println(param); DSERIAL.flush(); 426 | 427 | strupr(param); 428 | 429 | if (String(param) == PSTR("WRAPFORWARDFROMLEFT")) { 430 | scrollingLayer.setMode(wrapForwardFromLeft); 431 | } 432 | if (String(param) == PSTR("WRAPFORWARD")) { 433 | scrollingLayer.setMode(wrapForwardFromLeft); 434 | } 435 | if (String(param) == PSTR("BOUNCEFORWARD")) { 436 | scrollingLayer.setMode(bounceForward); 437 | } 438 | if (String(param) == PSTR("BOUNCEREVERSE")) { 439 | scrollingLayer.setMode(bounceReverse); 440 | } 441 | if (String(param) == PSTR("STOPPED")) { 442 | scrollingLayer.setMode(stopped); 443 | } 444 | if (String(param) == PSTR("OFF")) { 445 | scrollingLayer.setMode(off); 446 | } 447 | } 448 | else if (!strcmp_P(cmd, PSTR("TF")) || !strcmp_P(cmd, PSTR("TFONT"))) 449 | { 450 | //------------------------------ 451 | DSERIALprint(F("tFont: ")); 452 | DSERIAL.println(param); DSERIAL.flush(); 453 | 454 | strupr(param); 455 | 456 | if (String(param) == PSTR("FONT3X5")) { 457 | scrollingLayer.setFont(font3x5); 458 | } 459 | if (String(param) == PSTR("FONT5X7")) { 460 | scrollingLayer.setFont(font5x7); 461 | } 462 | if (String(param) == PSTR("FONT6X10")) { 463 | scrollingLayer.setFont(font6x10); 464 | } 465 | if (String(param) == PSTR("FONT8X13")) { 466 | scrollingLayer.setFont(font8x13); 467 | } 468 | if (String(param) == PSTR("GOHUFONT11")) { 469 | scrollingLayer.setFont(gohufont11); 470 | } 471 | if (String(param) == PSTR("GOHUFONT11B")) { 472 | scrollingLayer.setFont(gohufont11b); 473 | } 474 | } 475 | else if (!strcmp_P(cmd, PSTR("DEL")) || !strcmp_P(cmd, PSTR("RM"))) 476 | { 477 | //------------------------------ 478 | if (!sd.remove(param)) 479 | { 480 | DSERIALprint(F("Failed to delete file ")); 481 | DSERIAL.println(param); DSERIAL.flush(); 482 | } 483 | else 484 | { 485 | DSERIALprint(F("File ")); 486 | DSERIAL.print(param); 487 | DSERIALprintln(F(" deleted")); DSERIAL.flush(); 488 | } 489 | } 490 | else if (!strcmp_P(cmd, PSTR("MD")) || !strcmp_P(cmd, PSTR("MKDIR"))) 491 | { 492 | //------------------------------ 493 | if (!sd.mkdir(param, true)) 494 | { 495 | DSERIALprint(F("Failed to create directory ")); 496 | DSERIAL.println(param); DSERIAL.flush(); 497 | } 498 | else 499 | { 500 | DSERIALprint(F("Directory ")); 501 | DSERIAL.print(param); 502 | DSERIALprintln(F(" created")); DSERIAL.flush(); 503 | } 504 | } 505 | else if (!strcmp_P(cmd, PSTR("RD")) || !strcmp_P(cmd, PSTR("RMDIR"))) 506 | { 507 | //------------------------------ 508 | if (!sd.rmdir(param)) 509 | { 510 | DSERIALprint(F("Failed to remove directory ")); 511 | DSERIAL.println(param); DSERIAL.flush(); 512 | } 513 | else 514 | { 515 | DSERIALprint(F("Directory ")); 516 | DSERIAL.print(param); 517 | DSERIALprintln(F(" removed")); DSERIAL.flush(); 518 | } 519 | } 520 | else if (!strcmp_P(cmd, PSTR("RND")) || !strcmp_P(cmd, PSTR("RANDOM"))) 521 | { 522 | //------------------------------ 523 | sd.vwd()->getName(fname, 13); 524 | // if (sizeof(fname) > 1) { 525 | snprintf(curPath, sizeof(curPath), "/%s/", fname); 526 | // } 527 | curPath[strlen(curPath)] = '\0'; 528 | 529 | 530 | DSERIALprint(F("Displaying random gifs from ")); 531 | DSERIALprintln(F(curPath)); 532 | DSERIAL.flush(); 533 | num_files = enumerateGIFFiles(curPath, (bool) false); 534 | 535 | randomSeed(analogRead(5)); 536 | int index = random(num_files); 537 | 538 | while (1) 539 | { 540 | if (futureTime < millis()) 541 | { 542 | index = random(num_files); 543 | if (openGifFilenameByIndex((const char *) curPath, index) >= 0) 544 | { 545 | backgroundLayer.fillScreen(COLOR_BLACK); 546 | backgroundLayer.swapBuffers(); 547 | decoder.startDecoding(); 548 | futureTime = millis() + (DISPLAY_TIME_SECONDS * 1000); 549 | } 550 | } 551 | decoder.decodeFrame(); 552 | if (DSERIAL.available() > 0) 553 | { 554 | c = DSERIAL.read(); 555 | if (c == '\n' || c == '\r') break; 556 | } 557 | }; 558 | 559 | } 560 | else if (!strcmp_P(cmd, PSTR("SZ"))) 561 | { 562 | //------------------------------ 563 | // Filcnt = 0; 564 | if (!strcmp_P(param, PSTR("*"))) 565 | { 566 | count_files(&Filesleft, &Totalleft); 567 | sd.vwd()->rewind(); 568 | 569 | if (Filesleft > 0) 570 | { 571 | ZSERIAL.print(F("rz\r")); 572 | sendzrqinit(); 573 | delay(200); 574 | 575 | while (sd.vwd()->readDir(dir) == sizeof(*dir)) 576 | { 577 | // read next directory entry in current working directory 578 | 579 | // format file name 580 | SdFile::dirName(dir, fname); 581 | 582 | // open file 583 | if (!fout.open(fname, O_READ)) error(F("file.open failed")); 584 | 585 | else if (!fout.isDir()) 586 | { 587 | if (wcs(fname) == ERROR) 588 | { 589 | delay(500); 590 | fout.close(); 591 | break; 592 | } 593 | else delay(500); 594 | } 595 | 596 | fout.close(); 597 | } 598 | saybibi(); 599 | } 600 | else 601 | { 602 | DSERIALprintln(F("No files found to send")); 603 | } 604 | } 605 | else if (!fout.open(param, O_READ)) 606 | { 607 | DSERIALprintln(F("file.open failed")); DSERIAL.flush(); 608 | } 609 | else 610 | { 611 | // Start the ZMODEM transfer 612 | Filesleft = 1; 613 | Totalleft = fout.fileSize(); 614 | ZSERIAL.print(F("rz\r")); 615 | sendzrqinit(); 616 | delay(200); 617 | wcs(param); 618 | saybibi(); 619 | fout.close(); 620 | } 621 | } 622 | else if (!strcmp_P(cmd, PSTR("RZ"))) 623 | { 624 | //------------------------------ 625 | // DSERIALprintln(F("Receiving file...")); 626 | if (wcreceive(0, 0)) 627 | { 628 | DSERIALprintln(F("zmodem transfer failed")); DSERIAL.flush(); 629 | } 630 | else 631 | { 632 | DSERIALprintln(F("zmodem transfer successful")); 633 | } 634 | fout.flush(); 635 | fout.sync(); 636 | fout.close(); 637 | } 638 | } 639 | -------------------------------------------------------------------------------- /zmodem_sz.cpp: -------------------------------------------------------------------------------- 1 | /*% cc -compat -M2 -Ox -K -i -DTXBSIZE=16384 -DNFGVMIN -DREADCHECK sz.c -lx -o sz; size sz 2 | 3 | Following is used for testing, might not be reasonable for production 4 | <-xtx-*> cc -Osal -DTXBSIZE=32768 -DSV sz.c -lx -o $B/sz; size $B/sz 5 | 6 | **************************************************************************** 7 | * 8 | * sz.c By Chuck Forsberg, Omen Technology INC 9 | * 10 | **************************************************************************** 11 | * 12 | * Typical Unix/Xenix/Clone compiles: 13 | * 14 | * cc -O sz.c -o sz USG (SYS III/V) Unix 15 | * cc -O -DSV sz.c -o sz Sys V Release 2 with non-blocking input 16 | * Define to allow reverse channel checking 17 | * cc -O -DV7 sz.c -o sz Unix Version 7, 2.8 - 4.3 BSD 18 | * 19 | * cc -O -K -i -DNFGVMIN -DREADCHECK sz.c -lx -o sz Classic Xenix 20 | * 21 | * ln sz sb **** All versions **** 22 | * ln sz sx **** All versions **** 23 | * 24 | **************************************************************************** 25 | * 26 | * Typical VMS compile and install sequence: 27 | * 28 | * define LNK$LIBRARY SYS$LIBRARY:VAXCRTL.OLB 29 | * cc sz.c 30 | * cc vvmodem.c 31 | * link sz,vvmodem 32 | * sz :== $disk$user2:[username.subdir]sz.exe 33 | * 34 | * If you feel adventureous, remove the #define BADSYNC line 35 | * immediately following the #ifdef vax11c line! Some VMS 36 | * systems know how to fseek, some don't. 37 | * 38 | **************************************************************************** 39 | * 40 | * 41 | * A program for Unix to send files and commands to computers running 42 | * Professional-YAM, PowerCom, YAM, IMP, or programs supporting Y/XMODEM. 43 | * 44 | * Sz uses buffered I/O to greatly reduce CPU time compared to UMODEM. 45 | * 46 | * USG UNIX (3.0) ioctl conventions courtesy Jeff Martin 47 | * 48 | * 2.1x hacks to avoid VMS fseek() bogosity, allow input from pipe 49 | * -DBADSEEK -DTXBSIZE=32768 50 | * 2.x has mods for VMS flavor 51 | * 52 | * 1.34 implements tx backchannel garbage count and ZCRCW after ZRPOS 53 | * in accordance with the 7-31-87 ZMODEM Protocol Description 54 | */ 55 | 56 | #include "zmodem_config.h" 57 | #include "zmodem_fixes.h" 58 | 59 | #ifdef ARDUINO_SMALL_MEMORY_INCLUDE_SZ 60 | 61 | #define xsendline(c) sendline(c) 62 | 63 | #include "zmodem.h" 64 | #include "zmodem_zm.h" 65 | #include "zmodem_crc16.cpp" 66 | 67 | #include 68 | 69 | #define PATHLEN 64 70 | 71 | #define HOWMANY 2 72 | 73 | #define Txwindow 0 /* Control the size of the transmitted window */ 74 | #define Txwspac 0 /* Spacing between zcrcq requests */ 75 | unsigned Txwcnt; /* Counter used to space ack requests */ 76 | #define Lrxpos rxbytes 77 | extern long Lrxpos; /* Receiver's last reported offset */ 78 | 79 | int Filesleft = 0; 80 | long Totalleft = 0L; 81 | 82 | /* 83 | * Attention string to be executed by receiver to interrupt streaming data 84 | * when an error is detected. A pause (0336) may be needed before the 85 | * ^C (03) or after it. 86 | */ 87 | #ifdef READCHECK 88 | char Myattn[] = { 89 | 0 }; 90 | #else 91 | #ifdef USG 92 | char Myattn[] = { 93 | 03, 0336, 0 }; 94 | #else 95 | char Myattn[] = { 96 | 0 }; 97 | #endif 98 | #endif 99 | 100 | //FILE *in; 101 | 102 | #ifdef BADSEEK 103 | #define Canseek 0 /* 1: Can seek 0: only rewind -1: neither (pipe) */ 104 | #ifndef TXBSIZE 105 | #define TXBSIZE 16384 /* Must be power of two, < MAXINT */ 106 | #endif 107 | #else 108 | #define Canseek 1 /* 1: Can seek 0: only rewind -1: neither (pipe) */ 109 | #endif 110 | 111 | #ifdef TXBSIZE 112 | #define TXBMASK (TXBSIZE-1) 113 | #define Txb oneKbuf /* Circular buffer for file reads */ 114 | #define txbuf Txb /* Pointer to current file segment */ 115 | // Force the user to specify a buffer size 116 | //#else 117 | //char txbuf[1024]; 118 | #endif 119 | 120 | //long vpos = 0; /* Number of bytes read from file */ 121 | 122 | #define Modem2 0 /* XMODEM Protocol - don't send pathnames */ 123 | 124 | #define Ascii 0 /* Add CR's for brain damaged programs */ 125 | #define Fullname 0 /* transmit full pathname */ 126 | #define Unlinkafter 0 /* Unlink file after it is sent */ 127 | #define Dottoslash 0 /* Change foo.bar.baz to foo/bar/baz */ 128 | 129 | int errcnt=0; /* number of files unreadable */ 130 | #define blklen Blklen 131 | extern int blklen; /* length of transmitted records */ 132 | #define Optiong 0 /* Let it rip no wait for sector ACK's */ 133 | 134 | int Totsecs; /* total number of sectors this file */ 135 | //int Filcnt=0; /* count of number of files opened */ 136 | uint8_t Lfseen=0; 137 | #define Rxbuflen 16384 /* Receiver's max buffer length */ 138 | int Tframlen = 0; /* Override for tx frame length */ 139 | #define blkopt 0 /* Override value for zmodem blklen */ 140 | //int Rxflags = 0; 141 | #define bytcnt Bytesleft 142 | extern long bytcnt; 143 | //int Wantfcs32 = TRUE; /* want to send 32 bit FCS */ 144 | #define Lzconv 0 /* Local ZMODEM file conversion request */ 145 | 146 | #define Lskipnocor 0 147 | #define Lztrans 0 148 | 149 | //int Command; /* Send a command, then exit. */ 150 | //char *Cmdstr; /* Pointer to the command string */ 151 | //int Cmdtries = 11; 152 | //int Cmdack1; /* Rx ACKs command, then do it */ 153 | //int Exitcode = 0; 154 | #define Test 0 /* 1= Force receiver to send Attn, etc with qbf. */ 155 | /* 2= Character transparency test */ 156 | 157 | //char qbf[] = "The quick brown fox jumped over the lazy dog's back 1234567890\r\n"; 158 | 159 | long Lastsync; /* Last offset to which we got a ZRPOS */ 160 | uint8_t Beenhereb4; /* How many times we've been ZRPOS'd same place */ 161 | 162 | // Pete (El Supremo) 163 | _PROTOTYPE(int wcs , (const char *oname )); 164 | _PROTOTYPE(int wctxpn , (const char *name)); 165 | 166 | _PROTOTYPE(int wctx , (long flen )); 167 | _PROTOTYPE(int wcputsec , (char *buf , int sectnum , int cseclen )); 168 | _PROTOTYPE(int filbuf , (char *buf , int count )); 169 | _PROTOTYPE(int zfilbuf , (void)); 170 | _PROTOTYPE(void flushmo , (void)); 171 | _PROTOTYPE(void purgeline , (void)); 172 | _PROTOTYPE(void canit , (void)); 173 | 174 | //void zperr(); 175 | #define zperr(a, ... ) 176 | 177 | _PROTOTYPE(int sendzsinit , (void)); 178 | _PROTOTYPE(int zsendfile , (char *buf , int blen )); 179 | _PROTOTYPE(int zsendfdata , (void)); 180 | _PROTOTYPE(int getinsync , (int flag )); 181 | _PROTOTYPE(void saybibi , (void)); 182 | //_PROTOTYPE(void bttyout , (int c )); 183 | _PROTOTYPE(int zsendcmd , (char *buf , int blen )); 184 | 185 | 186 | #ifndef ARDUINO 187 | FILE *fout; 188 | #else 189 | extern SdFile fout; 190 | #endif 191 | 192 | int wcs(const char *oname) 193 | { 194 | // char name[PATHLEN]; 195 | 196 | // strcpy(name, oname); 197 | 198 | Eofseen = 0; 199 | // vpos = 0; 200 | 201 | switch (wctxpn(oname)) { 202 | case ERROR: 203 | return ERROR; 204 | case ZSKIP: 205 | return OK; 206 | } 207 | 208 | // ++Filcnt; 209 | if(!Zmodem && wctx(fout.fileSize())==ERROR) 210 | return ERROR; 211 | return 0; 212 | } 213 | 214 | 215 | /* 216 | * generate and transmit pathname block consisting of 217 | * pathname (null terminated), 218 | * file length, mode time and file mode in octal 219 | * as provided by the Unix fstat call. 220 | * N.B.: modifies the passed name, may extend it! 221 | */ 222 | int wctxpn(const char *name) 223 | { 224 | 225 | char *p, *q; 226 | 227 | //DSERIAL.println("\nwctxpn"); 228 | 229 | strcpy(txbuf,name); 230 | p = q = txbuf + strlen(txbuf)+1; 231 | //Pete (El Supremo) fix bug - was 1024, should be TXBSIZE?? 232 | while (q < (txbuf + TXBSIZE)) 233 | *q++ = 0; 234 | // if (!Ascii && (in!=stdin) && *name && fstat(fileno(in), &f)!= -1) 235 | if (!Ascii) 236 | // I will have to figure out how to convert the uSD date/time format to a UNIX epoch 237 | // sprintf(p, "%lu %lo %o 0 %d %ld", fout.fileSize(), 0L,0600, Filesleft, Totalleft); 238 | // Avoid sprintf to save memory for small boards. This sketch doesn't know what time it is anyway 239 | ultoa(fout.fileSize(), p, 10); 240 | strcat_P(p, PSTR(" 0 0 0 ")); 241 | q = p + strlen(p); 242 | ultoa(Filesleft, q, 10); 243 | strcat_P(q, PSTR(" ")); 244 | q = q + strlen(q); 245 | ultoa(Totalleft, q, 10); 246 | 247 | Totalleft -= fout.fileSize(); 248 | //DSERIAL.print(F("wctxpn sf = ")); 249 | //DSERIAL.print(sf); 250 | //DSERIAL.print(F(" length = ")); 251 | //DSERIAL.println(Totalleft); 252 | if (--Filesleft <= 0) 253 | Totalleft = 0; 254 | if (Totalleft < 0) 255 | Totalleft = 0; 256 | 257 | /* force 1k blocks if name won't fit in 128 byte block */ 258 | //Pete (El Supremo) This can't be right??! 259 | if (txbuf[125]) 260 | // blklen=1024; 261 | blklen = TXBSIZE; 262 | else { /* A little goodie for IMP/KMD */ 263 | blklen = 128; 264 | txbuf[127] = (fout.fileSize() + 127) >>7; 265 | txbuf[126] = (fout.fileSize() + 127) >>15; 266 | } 267 | return zsendfile(txbuf, 1+strlen(p)+(p-txbuf)); 268 | } 269 | 270 | 271 | int wctx(long flen) 272 | { 273 | int thisblklen; 274 | int sectnum, attempts, firstch; 275 | long charssent; 276 | 277 | //DSERIAL.println("\nwctx"); 278 | 279 | charssent = 0; 280 | firstsec=TRUE; 281 | thisblklen = blklen; 282 | vfile(F("wctx:file length=%ld"), flen); 283 | 284 | while ((firstch=readline(Rxtimeout))!=NAK && firstch != WANTCRC 285 | && firstch != WANTG && firstch!=TIMEOUT && firstch!=CAN) 286 | ; 287 | if (firstch==CAN) { 288 | zperr("Receiver CANcelled"); 289 | return ERROR; 290 | } 291 | if (firstch==WANTCRC) 292 | Crcflg=TRUE; 293 | if (firstch==WANTG) 294 | Crcflg=TRUE; 295 | sectnum=0; 296 | for (;;) { 297 | if (flen <= (charssent + 896L)) 298 | thisblklen = 128; 299 | if ( !filbuf(txbuf, thisblklen)) 300 | break; 301 | if (wcputsec(txbuf, ++sectnum, thisblklen)==ERROR) 302 | return ERROR; 303 | charssent += thisblklen; 304 | } 305 | //fclose(in); 306 | fout.close(); 307 | attempts=0; 308 | do { 309 | purgeline(); 310 | sendline(EOT); 311 | //fflush(stdout); 312 | ++attempts; 313 | } 314 | while ((firstch=(readline(Rxtimeout)) != ACK) && attempts < Tx_RETRYMAX); 315 | if (attempts == Tx_RETRYMAX) { 316 | zperr("No ACK on EOT"); 317 | return ERROR; 318 | } 319 | else 320 | return OK; 321 | } 322 | 323 | 324 | 325 | int wcputsec(char *buf,int sectnum,int cseclen) 326 | { 327 | int checksum, wcj; 328 | char *cp; 329 | unsigned oldcrc; 330 | int firstch; 331 | uint8_t attempts; 332 | 333 | firstch=0; /* part of logic to detect CAN CAN */ 334 | 335 | if (Verbose>2) 336 | fprintf(stderr, "Sector %3d %2dk\n", Totsecs, Totsecs/8 ); 337 | else if (Verbose>1) 338 | fprintf(stderr, "\rSector %3d %2dk ", Totsecs, Totsecs/8 ); 339 | for (attempts=0; attempts <= Tx_RETRYMAX; attempts++) { 340 | Lastrx= firstch; 341 | sendline(cseclen==1024?STX:SOH); 342 | sendline(sectnum); 343 | sendline(-sectnum -1); 344 | oldcrc=checksum=0; 345 | for (wcj=cseclen,cp=buf; --wcj>=0; ) { 346 | sendline(*cp); 347 | oldcrc=updcrc((0377& *cp), oldcrc); 348 | checksum += *cp++; 349 | } 350 | if (Crcflg) { 351 | oldcrc=updcrc(0,updcrc(0,oldcrc)); 352 | sendline((int)oldcrc>>8); 353 | sendline((int)oldcrc); 354 | } 355 | else 356 | sendline(checksum); 357 | 358 | if (Optiong) { 359 | firstsec = FALSE; 360 | return OK; 361 | } 362 | firstch = readline(Rxtimeout); 363 | gotnak: 364 | switch (firstch) { 365 | case CAN: 366 | if(Lastrx == CAN) { 367 | cancan: 368 | zperr("Cancelled"); 369 | return ERROR; 370 | } 371 | break; 372 | case TIMEOUT: 373 | zperr("Timeout on sector ACK"); 374 | continue; 375 | case WANTCRC: 376 | if (firstsec) 377 | Crcflg = TRUE; 378 | case NAK: 379 | zperr("NAK on sector"); 380 | continue; 381 | case ACK: 382 | firstsec=FALSE; 383 | Totsecs += (cseclen>>7); 384 | return OK; 385 | case ERROR: 386 | zperr("Got burst for sector ACK"); 387 | break; 388 | default: 389 | zperr("Got %02x for sector ACK", firstch); 390 | break; 391 | } 392 | for (;;) { 393 | Lastrx = firstch; 394 | if ((firstch = readline(Rxtimeout)) == TIMEOUT) 395 | break; 396 | if (firstch == NAK || firstch == WANTCRC) 397 | goto gotnak; 398 | if (firstch == CAN && Lastrx == CAN) 399 | goto cancan; 400 | } 401 | } 402 | zperr("Retry Count Exceeded"); 403 | return ERROR; 404 | } 405 | 406 | 407 | 408 | /* fill buf with count chars padding with ^Z for CPM */ 409 | int filbuf(char *buf,int count) 410 | { 411 | int c, m; 412 | 413 | //DSERIAL.println("\nfilbuf"); 414 | 415 | if ( !Ascii) { 416 | // m = read(fileno(in), buf, count); 417 | m = fout.read(buf, count); 418 | //DSERIAL.println(F("filbuf: '")); 419 | //for(int i=0;i=0) 452 | *buf++ = CPMEOF; 453 | return count; 454 | } 455 | 456 | 457 | 458 | /* Fill buffer with blklen chars */ 459 | int zfilbuf(void) 460 | { 461 | int n; 462 | 463 | // This code works: 464 | // n = fread(txbuf, 1, blklen, in); 465 | n = fout.read(txbuf,blklen); 466 | 467 | if (n < blklen) 468 | Eofseen = 1; 469 | return n; 470 | } 471 | 472 | /* Send file name and related info */ 473 | int zsendfile(char *buf, int blen) 474 | { 475 | int c; 476 | UNSL long crc; 477 | 478 | //DSERIAL.println(F("\nzsendfile")); 479 | 480 | for (;;) { 481 | Txhdr[ZF0] = Lzconv; /* file conversion request */ 482 | Txhdr[ZF1] = Lzmanag; /* file management request */ 483 | if (Lskipnocor) 484 | Txhdr[ZF1] |= ZMSKNOLOC; 485 | Txhdr[ZF2] = Lztrans; /* file transport request */ 486 | Txhdr[ZF3] = 0; 487 | zsbhdr(ZFILE, Txhdr); 488 | zsdata(buf, blen, ZCRCW); 489 | again: 490 | c = zgethdr(Rxhdr, 1); 491 | switch (c) { 492 | case ZRINIT: 493 | while ((c = readline(50)) > 0) 494 | if (c == ZPAD) { 495 | goto again; 496 | } 497 | /* **** FALL THRU TO **** */ 498 | default: 499 | continue; 500 | case ZCAN: 501 | case TIMEOUT: 502 | case ZABORT: 503 | case ZFIN: 504 | //DSERIAL.println(F("\nzsendfile - ZFIN")); 505 | 506 | return ERROR; 507 | case ZCRC: 508 | crc = 0xFFFFFFFFL; 509 | if (Canseek >= 0) { 510 | fout.seekSet(0); 511 | while (((c = fout.read()) != -1)) // && --Rxpos) 512 | crc = UPDC32(c, crc); 513 | crc = ~crc; 514 | // clearerr(in); /* Clear EOF */ 515 | //>>> Need to implement the seek 516 | // fseek(in, 0L, 0); 517 | fout.seekSet(0); 518 | } 519 | stohdr(crc); 520 | zsbhdr(ZCRC, Txhdr); 521 | goto again; 522 | case ZSKIP: 523 | fout.close(); 524 | //fclose(in); 525 | //DSERIAL.println(F("\nzsendfile - ZSKIP")); 526 | return c; 527 | case ZRPOS: 528 | /* 529 | * Suppress zcrcw request otherwise triggered by 530 | * lastyunc==bytcnt 531 | */ 532 | //>>> Need to implement the seek 533 | // if (Rxpos && fseek(in, Rxpos, 0)) 534 | if(Rxpos && !fout.seekSet(Rxpos)) 535 | return ERROR; 536 | Lastsync = (bytcnt = Txpos = Rxpos) -1; 537 | int ret = zsendfdata(); 538 | //DSERIAL.print(F("\nzsendfile - exit - ")); 539 | //DSERIAL.println(ret); 540 | return(ret); 541 | } 542 | } 543 | } 544 | 545 | 546 | 547 | /* Send the data in the file */ 548 | int zsendfdata(void) 549 | { 550 | int c, n; 551 | uint8_t e; 552 | int newcnt; 553 | uint8_t junkcount; /* Counts garbage chars received by TX */ 554 | 555 | //DSERIAL.print(F("\nzsendfdata: ")); 556 | //DSERIAL.print(F("number = ")); 557 | //DSERIAL.print(Filesleft+1); 558 | //DSERIAL.print(F(" length = ")); 559 | //DSERIAL.println(Totalleft); 560 | Lrxpos = 0; 561 | junkcount = 0; 562 | Beenhereb4 = FALSE; 563 | somemore: 564 | //if (setjmp(intrjmp)) { 565 | if (0) { 566 | waitack: 567 | junkcount = 0; 568 | c = getinsync(0); 569 | gotack: 570 | switch (c) { 571 | default: 572 | case ZCAN: 573 | fout.close(); 574 | //fclose(in); 575 | //DSERIAL.println(F("zsendfdata - error - 1")); 576 | return ERROR; 577 | case ZSKIP: 578 | fout.close(); 579 | //fclose(in); 580 | return c; 581 | case ZACK: 582 | case ZRPOS: 583 | break; 584 | case ZRINIT: 585 | return OK; 586 | } 587 | #ifdef READCHECK 588 | /* 589 | * If the reverse channel can be tested for data, 590 | * this logic may be used to detect error packets 591 | * sent by the receiver, in place of setjmp/longjmp 592 | * rdchk(fdes) returns non 0 if a character is available 593 | */ 594 | while (ZSERIAL.available()) { 595 | #ifdef SV 596 | switch (checked) 597 | #else 598 | switch (readline(1)) 599 | #endif 600 | { 601 | case CAN: 602 | case ZPAD: 603 | c = getinsync(1); 604 | goto gotack; 605 | case XOFF: /* Wait a while for an XON */ 606 | case XOFF|0200: 607 | readline(100); 608 | } 609 | } 610 | #endif 611 | } 612 | 613 | //DSERIAL.println("zsendfdata - 1"); 614 | 615 | // if ( !Fromcu) 616 | // signal(SIGINT, onintr); 617 | newcnt = Rxbuflen; 618 | Txwcnt = 0; 619 | stohdr(Txpos); 620 | zsbhdr(ZDATA, Txhdr); 621 | 622 | //DSERIAL.println("zsendfdata - 2"); 623 | 624 | do { 625 | n = zfilbuf(); 626 | // AHA - it reads the 18 chars here 627 | //DSERIAL.println(n); 628 | if (Eofseen) 629 | e = ZCRCE; 630 | else if (junkcount > 3) 631 | e = ZCRCW; 632 | else if (bytcnt == Lastsync) 633 | e = ZCRCW; 634 | else if (Rxbuflen && (newcnt -= n) <= 0) 635 | e = ZCRCW; 636 | else if (Txwindow && (Txwcnt += n) >= Txwspac) { 637 | Txwcnt = 0; 638 | e = ZCRCQ; 639 | } 640 | else 641 | e = ZCRCG; 642 | if (Verbose>1) 643 | fprintf(stderr, "\r%7ld ZMODEM%s ",Txpos, Crc32t?" CRC-32":""); 644 | zsdata(txbuf, n, e); 645 | bytcnt = Txpos += n; 646 | if (e == ZCRCW) 647 | goto waitack; 648 | #ifdef READCHECK 649 | /* 650 | * If the reverse channel can be tested for data, 651 | * this logic may be used to detect error packets 652 | * sent by the receiver, in place of setjmp/longjmp 653 | * rdchk(fdes) returns non 0 if a character is available 654 | */ 655 | // fflush(stdout); 656 | while (ZSERIAL.available()) { 657 | #ifdef SV 658 | switch (checked) 659 | #else 660 | switch (readline(1)) 661 | #endif 662 | { 663 | case CAN: 664 | case ZPAD: 665 | c = getinsync(1); 666 | if (c == ZACK) 667 | break; 668 | #ifdef TCFLSH 669 | ioctl(iofd, TCFLSH, 1); 670 | #endif 671 | /* zcrce - dinna wanna starta ping-pong game */ 672 | zsdata(txbuf, 0, ZCRCE); 673 | goto gotack; 674 | case XOFF: /* Wait a while for an XON */ 675 | case XOFF|0200: 676 | readline(100); 677 | default: 678 | ++junkcount; 679 | } 680 | } 681 | #endif /* READCHECK */ 682 | 683 | } while (!Eofseen); 684 | 685 | //DSERIAL.println("zsendfdata - 4"); 686 | 687 | // if ( !Fromcu) 688 | // signal(SIGINT, SIG_IGN); 689 | 690 | for (;;) { 691 | stohdr(Txpos); 692 | zsbhdr(ZEOF, Txhdr); 693 | switch (getinsync(0)) { 694 | case ZACK: 695 | //DSERIAL.println(F("zsendfdata - ZAK")); 696 | continue; 697 | case ZRPOS: 698 | //DSERIAL.println(F("zsendfdata - ZRPOS")); 699 | goto somemore; 700 | case ZRINIT: 701 | //DSERIAL.println(F("zsendfdata - OK")); 702 | return OK; 703 | case ZSKIP: 704 | fout.close(); 705 | //fclose(in); 706 | //DSERIAL.println(F("zsendfdata - ZSKIP")); 707 | return c; 708 | default: 709 | fout.close(); 710 | //fclose(in); 711 | //DSERIAL.println(F("zsendfdata - error - 2")); 712 | return ERROR; 713 | } 714 | } 715 | } 716 | 717 | 718 | 719 | 720 | /* 721 | * Respond to receiver's complaint, get back in sync with receiver 722 | */ 723 | int getinsync(int flag) 724 | { 725 | int c; 726 | 727 | for (;;) { 728 | if (Test) { 729 | //DSERIAL.println(F("***** Signal Caught *****")); 730 | Rxpos = 0; 731 | c = ZRPOS; 732 | } 733 | else 734 | c = zgethdr(Rxhdr, 0); 735 | switch (c) { 736 | case ZCAN: 737 | case ZABORT: 738 | case ZFIN: 739 | case TIMEOUT: 740 | //DSERIAL.println(F("getinsync - timeout")); 741 | return ERROR; 742 | case ZRPOS: 743 | /* ************************************* */ 744 | /* If sending to a buffered modem, you */ 745 | /* might send a break at this point to */ 746 | /* dump the modem's buffer. */ 747 | // clearerr(in); /* In case file EOF seen */ 748 | // if (fseek(in, Rxpos, 0)) { 749 | // seekSet returns true on success 750 | if(!fout.seekSet(Rxpos)) { 751 | //DSERIAL.println(F("getinsync - fseek")); 752 | return ERROR; 753 | } 754 | Eofseen = 0; 755 | bytcnt = Lrxpos = Txpos = Rxpos; 756 | if (Lastsync == Rxpos) { 757 | if (++Beenhereb4 > 4) 758 | if (blklen > 32) 759 | blklen /= 2; 760 | } 761 | Lastsync = Rxpos; 762 | return c; 763 | case ZACK: 764 | Lrxpos = Rxpos; 765 | if (flag || Txpos == Rxpos) 766 | return ZACK; 767 | continue; 768 | case ZRINIT: 769 | case ZSKIP: 770 | fout.close(); 771 | //fclose(in); 772 | return c; 773 | case ERROR: 774 | default: 775 | zsbhdr(ZNAK, Txhdr); 776 | continue; 777 | } 778 | } 779 | } 780 | 781 | // Dylan (monte_carlo_ecm, bitflipper, etc.) - I added this simple ZRQINIT string to trigger 782 | // terminal program's receive auto start feature. This was missing in the code as I found it. 783 | // All the terminal applications I tried would receive files anyway if I manually started 784 | // the download, but sending the ZRQINIT is the right way to initiate a ZMODEM transfer 785 | // according to the protocol documentation. 786 | 787 | #define ZRQINIT_STR F("\x2a\x2a\x18\x42" \ 788 | "\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30" \ 789 | "\x0d\x0a\x11") 790 | 791 | void sendzrqinit(void) 792 | { 793 | ZSERIAL.print(ZRQINIT_STR); 794 | } 795 | 796 | /* Say "bibi" to the receiver, try to do it cleanly */ 797 | void saybibi(void) 798 | { 799 | for (;;) { 800 | stohdr(0L); /* CAF Was zsbhdr - minor change */ 801 | zshhdr(ZFIN, Txhdr); /* to make debugging easier */ 802 | switch (zgethdr(Rxhdr, 0)) { 803 | case ZFIN: 804 | sendline('O'); 805 | sendline('O'); 806 | flushmo(); 807 | case ZCAN: 808 | case TIMEOUT: 809 | return; 810 | } 811 | } 812 | } 813 | 814 | #endif 815 | 816 | /* End of sz.c */ 817 | -------------------------------------------------------------------------------- /zmodem_zm.cpp: -------------------------------------------------------------------------------- 1 | // See this page for all code: http://www.raspberryginger.com/jbailey/minix/html/dir_acf1a49c3b8ff2cb9205e4a19757c0d6.html 2 | // From: http://www.raspberryginger.com/jbailey/minix/html/zm_8c-source.html 3 | // docs at: http://www.raspberryginger.com/jbailey/minix/html/zm_8c.html 4 | 5 | // Look at all the files: 6 | // http://www.raspberryginger.com/jbailey/minix/html/files.html 7 | 8 | #ifndef ZMODEM_ZM_CPP 9 | #define ZMODEM_ZM_CPP 10 | 11 | /* 12 | * Z M . C 13 | * ZMODEM protocol primitives 14 | * 05-09-88 Chuck Forsberg Omen Technology Inc 15 | * 16 | * Entry point Functions: 17 | * zsbhdr(type, hdr) send binary header 18 | * zshhdr(type, hdr) send hex header 19 | * zgethdr(hdr, eflag) receive header - binary or hex 20 | * zsdata(buf, len, frameend) send data 21 | * zrdata(buf, len) receive data 22 | * stohdr(pos) store position data in Txhdr 23 | * long rclhdr(hdr) recover position offset from header 24 | */ 25 | 26 | #ifdef ARDUINO 27 | #include "zmodem_fixes.h" 28 | #include "zmodem.h" 29 | #include "zmodem_crc16.cpp" 30 | #include "zmodem_zm.h" 31 | #else 32 | #ifndef CANFDX 33 | #include "zmodem.h" 34 | #endif 35 | #endif 36 | 37 | // Shared globals 38 | long Bytesleft; // from rz - Shared with sz bytcnt 39 | long rxbytes; // from rz - Shared with sz Lrxpos 40 | int Blklen; // from rz - Shared with sz blklen 41 | 42 | #define Rxtimeout 100 /* Tenths of seconds to wait for something */ 43 | #define Verbose 0 44 | 45 | // This buffer blends Txb (from sz) and secbuf (from rz) into a single buffer, saving 1K 46 | // of memory. 47 | char oneKbuf[1025]; 48 | 49 | /* Globals used by ZMODEM functions */ 50 | uint8_t Rxframeind; /* ZBIN ZBIN32, or ZHEX type of frame received */ 51 | uint8_t Rxtype; /* Type of header received */ 52 | int Rxcount; /* Count of data bytes received */ 53 | char Rxhdr[4]; /* Received header */ 54 | char Txhdr[4]; /* Transmitted header */ 55 | long Rxpos; /* Received file position */ 56 | long Txpos; /* Transmitted file position */ 57 | int8_t Txfcs32; /* TRUE means send binary frames with 32 bit FCS */ 58 | int8_t Crc32t; /* Display flag indicating 32 bit CRC being sent */ 59 | int8_t Crc32; /* Display flag indicating 32 bit CRC being received */ 60 | //int Znulls; /* Number of nulls to send at beginning of ZDATA hdr */ 61 | char Attn[ZATTNLEN+1]; /* Attention string rx sends to tx on err */ 62 | 63 | char zconv; /* ZMODEM file conversion request */ 64 | char zmanag; /* ZMODEM file management request */ 65 | char ztrans; /* ZMODEM file transport request */ 66 | uint8_t Zctlesc; /* Encode control characters */ 67 | #define Zrwindow 1400 /* RX window size (controls garbage count) */ 68 | //int Nozmodem = 0; /* If invoked as "rb" */ 69 | int lastsent; /* Last char we sent */ 70 | uint8_t Not8bit; /* Seven bits seen on header */ 71 | //char Lzmanag; /* Local ZMODEM file management request */ 72 | //int Restricted = 0; /* restricted; no /.. or ../ in filenames */ 73 | //int Quiet=0; /* overrides logic that would otherwise set verbose */ 74 | uint8_t Eofseen; /* EOF seen on input set by zfilbuf */ 75 | 76 | 77 | uint8_t firstsec; 78 | char Lastrx; 79 | char Crcflg; 80 | uint8_t errors; 81 | 82 | // Dylan (monte_carlo_ecm, bitflipper, etc.) - Removing this for release to save 83 | // memory, only used for debugging 84 | /* 85 | char *frametypes[] = { 86 | (char *)"Carrier Lost", // -3 87 | (char *)"TIMEOUT", // -2 88 | (char *)"ERROR", // -1 89 | #define FTOFFSET 3 90 | (char *)"ZRQINIT", 91 | (char *)"ZRINIT", 92 | (char *)"ZSINIT", 93 | (char *)"ZACK", 94 | (char *)"ZFILE", 95 | (char *)"ZSKIP", 96 | (char *)"ZNAK", 97 | (char *)"ZABORT", 98 | (char *)"ZFIN", 99 | (char *)"ZRPOS", 100 | (char *)"ZDATA", 101 | (char *)"ZEOF", 102 | (char *)"ZFERR", 103 | (char *)"ZCRC", 104 | (char *)"ZCHALLENGE", 105 | (char *)"ZCOMPL", 106 | (char *)"ZCAN", 107 | (char *)"ZFREECNT", 108 | (char *)"ZCOMMAND", 109 | (char *)"ZSTDERR", 110 | (char *)"xxxxx" 111 | #define FRTYPES 22 // Total number of frame types in this array 112 | // not including psuedo negative entries 113 | }; 114 | */ 115 | #define badcrc F("Bad CRC"); 116 | 117 | 118 | 119 | 120 | 121 | /* Send ZMODEM binary header hdr of type type */ 122 | void zsbhdr(int type, char *hdr) 123 | { 124 | 125 | 126 | vfile(F("zsbhdr: %s %lx"), frametypes[type+FTOFFSET], rclhdr(hdr)); 127 | /* if (type == ZDATA) 128 | for (n = Znulls; --n >=0; ) 129 | xsendline(0); 130 | */ 131 | xsendline(ZPAD); 132 | xsendline(ZDLE); 133 | //Pete (El Supremo) This looks wrong but it is correct - the code fails if == is used 134 | if ((Crc32t = Txfcs32)) { 135 | int n; 136 | UNSL long crc; 137 | 138 | xsendline(ZBIN32); 139 | zsendline(type); 140 | crc = 0xFFFFFFFFL; 141 | crc = UPDC32(type, crc); 142 | 143 | for (n=4; --n >= 0; ++hdr) { 144 | crc = UPDC32((0377 & *hdr), crc); 145 | zsendline(*hdr); 146 | } 147 | crc = ~crc; 148 | for (n=4; --n >= 0;) { 149 | zsendline((int)crc); 150 | crc >>= 8; 151 | } 152 | } else { 153 | int n; 154 | unsigned short crc; 155 | 156 | xsendline(ZBIN); 157 | zsendline(type); 158 | crc = updcrc(type, 0); 159 | 160 | for (n=4; --n >= 0; ++hdr) { 161 | zsendline(*hdr); 162 | crc = updcrc((0377& *hdr), crc); 163 | } 164 | crc = updcrc(0,updcrc(0,crc)); 165 | zsendline(crc>>8); 166 | zsendline(crc); 167 | } 168 | if (type != ZDATA) 169 | flushmo(); 170 | } 171 | 172 | 173 | /* Send ZMODEM HEX header hdr of type type */ 174 | void zshhdr(int type,char *hdr) 175 | { 176 | int n; 177 | unsigned short crc; 178 | 179 | vfile(F("zshhdr: %s %lx"), frametypes[type+FTOFFSET], rclhdr(hdr)); 180 | sendline(ZPAD); 181 | sendline(ZPAD); 182 | sendline(ZDLE); 183 | sendline(ZHEX); 184 | zputhex(type); 185 | Crc32t = 0; 186 | 187 | crc = updcrc(type, 0); 188 | for (n=4; --n >= 0; ++hdr) { 189 | zputhex(*hdr); 190 | crc = updcrc((0377 & *hdr), crc); 191 | } 192 | crc = updcrc(0,updcrc(0,crc)); 193 | zputhex(crc>>8); 194 | zputhex(crc); 195 | 196 | /* Make it printable on remote machine */ 197 | sendline(015); 198 | sendline(0212); 199 | /* 200 | * Uncork the remote in case a fake XOFF has stopped data flow 201 | */ 202 | if (type != ZFIN && type != ZACK) 203 | sendline(021); 204 | flushmo(); 205 | } 206 | 207 | 208 | /* 209 | * Send binary array buf of length length, with ending ZDLE sequence frameend 210 | */ 211 | /* 212 | static char *Zendnames[] = { 213 | (char *)"ZCRCE", 214 | (char *)"ZCRCG", 215 | (char *)"ZCRCQ", 216 | (char *)"ZCRCW" 217 | }; 218 | */ 219 | 220 | void zsdata(char *buf,int length,int frameend) 221 | { 222 | 223 | vfile(F("zsdata: %d %s"), length, Zendnames[(frameend-ZCRCE)&3]); 224 | if (Crc32t) { 225 | int c; 226 | UNSL long crc; 227 | 228 | crc = 0xFFFFFFFFL; 229 | for (;--length >= 0; ++buf) { 230 | c = *buf & 0377; 231 | if (c & 0140) 232 | xsendline(lastsent = c); 233 | else 234 | zsendline(c); 235 | crc = UPDC32(c, crc); 236 | } 237 | xsendline(ZDLE); 238 | xsendline(frameend); 239 | crc = UPDC32(frameend, crc); 240 | 241 | crc = ~crc; 242 | for (length=4; --length >= 0;) { 243 | zsendline((int)crc); 244 | crc >>= 8; 245 | } 246 | } else { 247 | unsigned short crc; 248 | 249 | crc = 0; 250 | for (;--length >= 0; ++buf) { 251 | zsendline(*buf); 252 | crc = updcrc((0377 & *buf), crc); 253 | } 254 | xsendline(ZDLE); 255 | xsendline(frameend); 256 | crc = updcrc(frameend, crc); 257 | 258 | crc = updcrc(0,updcrc(0,crc)); 259 | zsendline(crc>>8); 260 | zsendline(crc); 261 | } 262 | if (frameend == ZCRCW) { 263 | xsendline(XON); 264 | flushmo(); 265 | } 266 | } 267 | 268 | /* 269 | * Receive array buf of max length with ending ZDLE sequence 270 | * and CRC. Returns the ending character or error code. 271 | * NB: On errors may store length+1 bytes! 272 | */ 273 | int zrdata(char *buf,int length) 274 | { 275 | int c; 276 | char *end; 277 | int d; 278 | 279 | if (Rxframeind == ZBIN32) { 280 | UNSL long crc; 281 | 282 | crc = 0xFFFFFFFFL; 283 | Rxcount = 0; 284 | end = buf + length; 285 | while (buf <= end) { 286 | if ((c = zdlread()) & ~0377) { 287 | crcfoo32: 288 | switch (c) { 289 | case GOTCRCE: 290 | case GOTCRCG: 291 | case GOTCRCQ: 292 | case GOTCRCW: 293 | d = c; 294 | c &= 0377; 295 | crc = UPDC32(c, crc); 296 | if ((c = zdlread()) & ~0377) 297 | goto crcfoo32; 298 | crc = UPDC32(c, crc); 299 | if ((c = zdlread()) & ~0377) 300 | goto crcfoo32; 301 | crc = UPDC32(c, crc); 302 | if ((c = zdlread()) & ~0377) 303 | goto crcfoo32; 304 | crc = UPDC32(c, crc); 305 | if ((c = zdlread()) & ~0377) 306 | goto crcfoo32; 307 | crc = UPDC32(c, crc); 308 | if (crc != 0xDEBB20E3) { 309 | zperr(badcrc); 310 | return ERROR; 311 | } 312 | Rxcount = length - (end - buf); 313 | vfile(F("zrdat32: %d %s"), Rxcount, 314 | Zendnames[(d-GOTCRCE)&3]); 315 | return d; 316 | case GOTCAN: 317 | zperr("Sender Canceled"); 318 | return ZCAN; 319 | case TIMEOUT: 320 | zperr("TIMEOUT"); 321 | return c; 322 | default: 323 | zperr("Bad data subpacket"); 324 | return c; 325 | } 326 | } 327 | *buf++ = c; 328 | crc = UPDC32(c, crc); 329 | } 330 | zperr("Data subpacket too long"); 331 | return ERROR; 332 | } else { 333 | unsigned short crc; 334 | 335 | crc = Rxcount = 0; 336 | end = buf + length; 337 | while (buf <= end) { 338 | if ((c = zdlread()) & ~0377) { 339 | crcfoo16: 340 | switch (c) { 341 | case GOTCRCE: 342 | case GOTCRCG: 343 | case GOTCRCQ: 344 | case GOTCRCW: 345 | crc = updcrc((d=c)&0377, crc); 346 | if ((c = zdlread()) & ~0377) 347 | goto crcfoo16; 348 | crc = updcrc(c, crc); 349 | if ((c = zdlread()) & ~0377) 350 | goto crcfoo16; 351 | crc = updcrc(c, crc); 352 | if (crc & 0xFFFF) { 353 | zperr(badcrc); 354 | return ERROR; 355 | } 356 | Rxcount = length - (end - buf); 357 | vfile(F("zrdata: %d %s"), Rxcount, 358 | Zendnames[(d-GOTCRCE)&3]); 359 | return d; 360 | case GOTCAN: 361 | zperr("Sender Canceled"); 362 | return ZCAN; 363 | case TIMEOUT: 364 | zperr("TIMEOUT"); 365 | return c; 366 | default: 367 | zperr("Bad data subpacket"); 368 | return c; 369 | } 370 | } 371 | *buf++ = c; 372 | crc = updcrc(c, crc); 373 | } 374 | zperr("Data subpacket too long"); 375 | return ERROR; 376 | } 377 | } 378 | 379 | /* 380 | * Read a ZMODEM header to hdr, either binary or hex. 381 | * eflag controls local display of non zmodem characters: 382 | * 0: no display 383 | * 1: display printing characters only 384 | * 2: display all non ZMODEM characters 385 | * On success, set Zmodem to 1, set Rxpos and return type of header. 386 | * Otherwise return negative on error. 387 | * Return ERROR instantly if ZCRCW sequence, for fast error recovery. 388 | */ 389 | int zgethdr(char *hdr,int eflag) 390 | { 391 | int c, n, cancount; 392 | 393 | n = Zrwindow; //+ Baudrate; /* Max bytes before start of frame */ 394 | Rxframeind = Rxtype = 0; 395 | 396 | startover: 397 | cancount = 5; 398 | again: 399 | /* Return immediate ERROR if ZCRCW sequence seen */ 400 | ZSERIAL.setTimeout(Rxtimeout * 100); 401 | c = readline(Rxtimeout); 402 | ZSERIAL.setTimeout(TYPICAL_SERIAL_TIMEOUT); 403 | 404 | switch (c) { 405 | case RCDO: 406 | case TIMEOUT: 407 | goto fifi; 408 | case CAN: 409 | gotcan: 410 | if (--cancount <= 0) { 411 | c = ZCAN; 412 | goto fifi; 413 | } 414 | switch (c = readline(1)) { 415 | case TIMEOUT: 416 | goto again; 417 | case ZCRCW: 418 | c = ERROR; 419 | /* **** FALL THRU TO **** */ 420 | case RCDO: 421 | goto fifi; 422 | default: 423 | break; 424 | case CAN: 425 | if (--cancount <= 0) { 426 | c = ZCAN; 427 | goto fifi; 428 | } 429 | goto again; 430 | } 431 | /* **** FALL THRU TO **** */ 432 | default: 433 | agn2: 434 | if ( --n == 0) { 435 | zperr("Garbage count exceeded"); 436 | return(ERROR); 437 | } 438 | if (eflag && ((c &= 0177) & 0140)) 439 | bttyout(c); 440 | else if (eflag > 1) 441 | bttyout(c); 442 | #ifdef UNIX 443 | fflush(stderr); 444 | #endif 445 | goto startover; 446 | case ZPAD|0200: /* This is what we want. */ 447 | Not8bit = c; 448 | case ZPAD: /* This is what we want. */ 449 | break; 450 | } 451 | cancount = 5; 452 | splat: 453 | switch (c = noxrd7()) { 454 | case ZPAD: 455 | goto splat; 456 | case RCDO: 457 | case TIMEOUT: 458 | goto fifi; 459 | default: 460 | goto agn2; 461 | case ZDLE: /* This is what we want. */ 462 | break; 463 | } 464 | 465 | switch (c = noxrd7()) { 466 | case RCDO: 467 | case TIMEOUT: 468 | goto fifi; 469 | case ZBIN: 470 | Rxframeind = ZBIN; 471 | Crc32 = FALSE; 472 | c = zrbhdr(hdr); 473 | break; 474 | case ZBIN32: 475 | Crc32 = Rxframeind = ZBIN32; 476 | c = zrbhdr32(hdr); 477 | break; 478 | case ZHEX: 479 | Rxframeind = ZHEX; 480 | Crc32 = FALSE; 481 | c = zrhhdr(hdr); 482 | break; 483 | case CAN: 484 | goto gotcan; 485 | default: 486 | goto agn2; 487 | } 488 | Rxpos = hdr[ZP3] & 0377; 489 | Rxpos = (Rxpos<<8) + (hdr[ZP2] & 0377); 490 | Rxpos = (Rxpos<<8) + (hdr[ZP1] & 0377); 491 | Rxpos = (Rxpos<<8) + (hdr[ZP0] & 0377); 492 | fifi: 493 | 494 | switch (c) { 495 | case GOTCAN: 496 | c = ZCAN; 497 | /* **** FALL THRU TO **** */ 498 | case ZNAK: 499 | case ZCAN: 500 | case ERROR: 501 | case TIMEOUT: 502 | case RCDO: 503 | // zperr("Got %s", frametypes[c+FTOFFSET]); 504 | /* **** FALL THRU TO **** */ 505 | // default: 506 | // if (c >= -3 && c <= FRTYPES) 507 | // vfile(F("zgethdr: %s %lx"), frametypes[c+FTOFFSET], Rxpos); 508 | // else 509 | // vfile(F("zgethdr: %d %lx"), c, Rxpos); 510 | break; 511 | } 512 | return c; 513 | } 514 | 515 | //#endif 516 | 517 | /* Receive a binary style header (type and position) */ 518 | int zrbhdr(char *hdr) 519 | { 520 | int c, n; 521 | unsigned short crc; 522 | 523 | if ((c = zdlread()) & ~0377) 524 | return c; 525 | Rxtype = c; 526 | crc = updcrc(c, 0); 527 | 528 | for (n=4; --n >= 0; ++hdr) { 529 | if ((c = zdlread()) & ~0377) 530 | return c; 531 | crc = updcrc(c, crc); 532 | *hdr = c; 533 | } 534 | if ((c = zdlread()) & ~0377) 535 | return c; 536 | crc = updcrc(c, crc); 537 | if ((c = zdlread()) & ~0377) 538 | return c; 539 | crc = updcrc(c, crc); 540 | if (crc & 0xFFFF) { 541 | zperr(badcrc); 542 | return ERROR; 543 | } 544 | #ifdef ZMODEM 545 | Protocol = ZMODEM; 546 | #endif 547 | // Zmodem = 1; 548 | return Rxtype; 549 | } 550 | 551 | 552 | 553 | /* Receive a binary style header (type and position) with 32 bit FCS */ 554 | int zrbhdr32(char *hdr) 555 | { 556 | int c, n; 557 | UNSL long crc; 558 | 559 | if ((c = zdlread()) & ~0377) 560 | return c; 561 | Rxtype = c; 562 | crc = 0xFFFFFFFFL; 563 | crc = UPDC32(c, crc); 564 | #ifdef DEBUGZ 565 | vfile(F("zrbhdr32 c=%X crc=%lX"), c, crc); 566 | #endif 567 | 568 | for (n=4; --n >= 0; ++hdr) { 569 | if ((c = zdlread()) & ~0377) 570 | return c; 571 | crc = UPDC32(c, crc); 572 | *hdr = c; 573 | #ifdef DEBUGZ 574 | vfile(F("zrbhdr32 c=%X crc=%lX"), c, crc); 575 | #endif 576 | } 577 | for (n=4; --n >= 0;) { 578 | if ((c = zdlread()) & ~0377) 579 | return c; 580 | crc = UPDC32(c, crc); 581 | #ifdef DEBUGZ 582 | vfile(F("zrbhdr32 c=%X crc=%lX"), c, crc); 583 | #endif 584 | } 585 | if (crc != 0xDEBB20E3) { 586 | zperr(badcrc); 587 | return ERROR; 588 | } 589 | #ifdef ZMODEM 590 | Protocol = ZMODEM; 591 | #endif 592 | // Zmodem = 1; 593 | return Rxtype; 594 | } 595 | 596 | 597 | 598 | 599 | /* Receive a hex style header (type and position) */ 600 | int zrhhdr(char *hdr) 601 | { 602 | int c; 603 | unsigned short crc; 604 | int n; 605 | 606 | if ((c = zgethex()) < 0) 607 | return c; 608 | Rxtype = c; 609 | crc = updcrc(c, 0); 610 | 611 | for (n=4; --n >= 0; ++hdr) { 612 | if ((c = zgethex()) < 0) 613 | return c; 614 | crc = updcrc(c, crc); 615 | *hdr = c; 616 | } 617 | if ((c = zgethex()) < 0) 618 | return c; 619 | crc = updcrc(c, crc); 620 | if ((c = zgethex()) < 0) 621 | return c; 622 | crc = updcrc(c, crc); 623 | if (crc & 0xFFFF) { 624 | zperr(badcrc); 625 | return ERROR; 626 | } 627 | switch ( c = readline(1)) { 628 | case 0215: 629 | Not8bit = c; 630 | /* **** FALL THRU TO **** */ 631 | case 015: 632 | /* Throw away possible cr/lf */ 633 | switch (c = readline(1)) { 634 | case 012: 635 | Not8bit |= c; 636 | } 637 | } 638 | #ifdef ZMODEM 639 | Protocol = ZMODEM; 640 | #endif 641 | // Zmodem = 1; 642 | return Rxtype; 643 | } 644 | 645 | /* Send a byte as two hex digits */ 646 | /*void zputhex(int c) 647 | { 648 | static char digits[] = "0123456789abcdef"; 649 | 650 | if (Verbose>8) 651 | vfile(F("zputhex: %02X"), c); 652 | sendline(digits[(c&0xF0)>>4]); 653 | sendline(digits[(c)&0xF]); 654 | } */ 655 | 656 | PROGMEM static const char digits[17] = "0123456789abcdef"; 657 | 658 | void zputhex(int c) 659 | { 660 | // static char digits[] = "0123456789abcdef"; 661 | 662 | if (Verbose>8) 663 | vfile(F("zputhex: %02X"), c); 664 | sendline(pgm_read_byte(digits+((c&0xF0)>>4))); 665 | sendline(pgm_read_byte(digits+((c)&0xF))); 666 | } 667 | 668 | /* 669 | * Send character c with ZMODEM escape sequence encoding. 670 | * Escape XON, XOFF. Escape CR following @ (Telenet net escape) 671 | */ 672 | int zsendline2(int c) 673 | { 674 | 675 | /* Quick check for non control characters */ 676 | if (c & 0140) 677 | xsendline(lastsent = c); 678 | else { 679 | switch (c &= 0377) { 680 | case ZDLE: 681 | xsendline(ZDLE); 682 | xsendline (lastsent = (c ^= 0100)); 683 | break; 684 | case 015: 685 | case 0215: 686 | if (!Zctlesc && (lastsent & 0177) != '@') 687 | goto sendit; 688 | /* **** FALL THRU TO **** */ 689 | case 020: 690 | case 021: 691 | case 023: 692 | case 0220: 693 | case 0221: 694 | case 0223: 695 | xsendline(ZDLE); 696 | c ^= 0100; 697 | sendit: 698 | xsendline(lastsent = c); 699 | break; 700 | default: 701 | if (Zctlesc && ! (c & 0140)) { 702 | xsendline(ZDLE); 703 | c ^= 0100; 704 | } 705 | xsendline(lastsent = c); 706 | } 707 | } 708 | } 709 | 710 | 711 | /* Decode two lower case hex digits into an 8 bit byte value */ 712 | 713 | int zgethex(void) 714 | { 715 | int c, n; 716 | 717 | if ((c = noxrd7()) < 0) 718 | return c; 719 | n = c - '0'; 720 | if (n > 9) 721 | n -= ('a' - ':'); 722 | if (n & ~0xF) 723 | return ERROR; 724 | if ((c = noxrd7()) < 0) 725 | return c; 726 | c -= '0'; 727 | if (c > 9) 728 | c -= ('a' - ':'); 729 | if (c & ~0xF) 730 | return ERROR; 731 | c += (n<<4); 732 | return c; 733 | } 734 | 735 | 736 | 737 | /* 738 | * Read a byte, checking for ZMODEM escape encoding 739 | * including CAN*5 which represents a quick abort 740 | */ 741 | /* 742 | int zdlread(void) 743 | { 744 | int c; 745 | 746 | again: 747 | // Quick check for non control characters 748 | if ((c = readline(Rxtimeout)) & 0140) 749 | return c; 750 | switch (c) { 751 | case ZDLE: 752 | break; 753 | case 023: 754 | case 0223: 755 | case 021: 756 | case 0221: 757 | goto again; 758 | default: 759 | if (Zctlesc && !(c & 0140)) { 760 | goto again; 761 | } 762 | return c; 763 | } 764 | again2: 765 | if ((c = readline(Rxtimeout)) < 0) 766 | return c; 767 | if (c == CAN && (c = readline(Rxtimeout)) < 0) 768 | return c; 769 | if (c == CAN && (c = readline(Rxtimeout)) < 0) 770 | return c; 771 | if (c == CAN && (c = readline(Rxtimeout)) < 0) 772 | return c; 773 | switch (c) { 774 | case CAN: 775 | return GOTCAN; 776 | case ZCRCE: 777 | case ZCRCG: 778 | case ZCRCQ: 779 | case ZCRCW: 780 | return (c | GOTOR); 781 | case ZRUB0: 782 | return 0177; 783 | case ZRUB1: 784 | return 0377; 785 | case 023: 786 | case 0223: 787 | case 021: 788 | case 0221: 789 | goto again2; 790 | default: 791 | if (Zctlesc && ! (c & 0140)) { 792 | goto again2; 793 | } 794 | if ((c & 0140) == 0100) 795 | return (c ^ 0100); 796 | break; 797 | } 798 | if (Verbose>1) 799 | zperr("Bad escape sequence %x", c); 800 | return ERROR; 801 | } 802 | */ 803 | int zdlread2(int c) 804 | { 805 | again: 806 | // Quick check for non control characters 807 | 808 | switch (c) { 809 | case ZDLE: 810 | break; 811 | case 023: 812 | case 0223: 813 | case 021: 814 | case 0221: 815 | if ((c = readline(Rxtimeout)) & 0140) 816 | return c; 817 | goto again; 818 | default: 819 | if (Zctlesc && !(c & 0140)) { 820 | if ((c = readline(Rxtimeout)) & 0140) 821 | return c; 822 | goto again; 823 | } 824 | return c; 825 | } 826 | again2: 827 | if ((c = readline(Rxtimeout)) < 0) 828 | return c; 829 | if (c == CAN && (c = readline(Rxtimeout)) < 0) 830 | return c; 831 | if (c == CAN && (c = readline(Rxtimeout)) < 0) 832 | return c; 833 | if (c == CAN && (c = readline(Rxtimeout)) < 0) 834 | return c; 835 | switch (c) { 836 | case CAN: 837 | return GOTCAN; 838 | case ZCRCE: 839 | case ZCRCG: 840 | case ZCRCQ: 841 | case ZCRCW: 842 | return (c | GOTOR); 843 | case ZRUB0: 844 | return 0177; 845 | case ZRUB1: 846 | return 0377; 847 | case 023: 848 | case 0223: 849 | case 021: 850 | case 0221: 851 | goto again2; 852 | default: 853 | if (Zctlesc && ! (c & 0140)) { 854 | goto again2; 855 | } 856 | if ((c & 0140) == 0100) 857 | return (c ^ 0100); 858 | break; 859 | } 860 | if (Verbose>1) 861 | zperr("Bad escape sequence %x", c); 862 | return ERROR; 863 | } 864 | 865 | /* 866 | * Read a character from the modem line with timeout. 867 | * Eat parity, XON and XOFF characters. 868 | */ 869 | int noxrd7(void) 870 | { 871 | int c; 872 | 873 | for (;;) { 874 | if ((c = readline(Rxtimeout)) < 0) 875 | return c; 876 | switch (c &= 0177) { 877 | case XON: 878 | case XOFF: 879 | continue; 880 | default: 881 | if (Zctlesc && !(c & 0140)) 882 | continue; 883 | case '\r': 884 | case '\n': 885 | case ZDLE: 886 | return c; 887 | } 888 | } 889 | } 890 | 891 | 892 | 893 | /* Store long integer pos in Txhdr */ 894 | void stohdr(long pos) 895 | { 896 | Txhdr[ZP0] = pos; 897 | Txhdr[ZP1] = pos>>8; 898 | Txhdr[ZP2] = pos>>16; 899 | Txhdr[ZP3] = pos>>24; 900 | } 901 | 902 | 903 | #ifndef NOTDEF 904 | /* Recover a long integer from a header */ 905 | long rclhdr(char *hdr) 906 | { 907 | long l; 908 | 909 | l = (hdr[ZP3] & 0377); 910 | l = (l << 8) | (hdr[ZP2] & 0377); 911 | l = (l << 8) | (hdr[ZP1] & 0377); 912 | l = (l << 8) | (hdr[ZP0] & 0377); 913 | return l; 914 | } 915 | #endif 916 | 917 | /* 918 | * Send a character to modem. Small is beautiful. 919 | */ 920 | // Why was this called sendline ?? 921 | //void sendline(int c) 922 | //{ 923 | // ZSERIAL.write(c & 0xFF); 924 | // ZSERIAL.write(char(c)); 925 | // ZSERIAL.flush(); 926 | 927 | //DSERIAL.print("SEND: "); 928 | //DSERIAL.print(c, HEX); 929 | //DSERIAL.println(); 930 | 931 | //} 932 | /* 933 | //>>> Needs to be fixed up - see the original in rz 934 | // like sendline, this does not read a line! 935 | int readline(int timeout) 936 | { 937 | long then; 938 | unsigned char c; 939 | 940 | then = millis(); 941 | while(ZSERIAL.available() < 1) { 942 | if(millis() - then > (unsigned int)timeout*100UL) { 943 | DSERIAL.println("readline - TIMEOUT"); 944 | return(TIMEOUT); 945 | } 946 | } 947 | c = ZSERIAL.read(); 948 | //DSERIAL.print("READ: "); 949 | //DSERIAL.print(c, HEX); 950 | //DSERIAL.println(); 951 | return(c); 952 | } 953 | */ 954 | 955 | /* 956 | * Purge the modem input queue of all characters 957 | */ 958 | void purgeline(void) 959 | { 960 | while(ZSERIAL.available())ZSERIAL.read(); 961 | } 962 | 963 | /* 964 | * Local console output simulation 965 | */ 966 | void bttyout(int c) 967 | { 968 | #ifndef ARDUINO 969 | if (Verbose || Fromcu) 970 | putc(c, stderr); 971 | #endif 972 | } 973 | 974 | void flushmo(void) 975 | { 976 | ZSERIAL.flush(); 977 | } 978 | 979 | 980 | 981 | /* send cancel string to get the other end to shut up */ 982 | void canit(void) 983 | { 984 | for (int i=0; i < 10; ++i) { 985 | ZSERIAL.write(24); 986 | } 987 | for (int i=0; i < 10; ++i) { 988 | ZSERIAL.write(8); 989 | } 990 | ZSERIAL.flush(); 991 | } 992 | 993 | /* End of zm.c */ 994 | #endif 995 | -------------------------------------------------------------------------------- /GifDecoder_Impl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Animated GIFs Display Code for SmartMatrix and 32x32 RGB LED Panels 3 | * 4 | * This file contains code to parse animated GIF files 5 | * 6 | * Written by: Craig A. Lindley 7 | * 8 | * Copyright (c) 2014 Craig A. Lindley 9 | * Minor modifications by Louis Beaudoin (pixelmatix) 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 12 | * this software and associated documentation files (the "Software"), to deal in 13 | * the Software without restriction, including without limitation the rights to 14 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 15 | * the Software, and to permit persons to whom the Software is furnished to do so, 16 | * subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 23 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 24 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 25 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 26 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | */ 28 | 29 | #define GIFDEBUG 0 30 | 31 | #if defined (ARDUINO) 32 | #include 33 | #elif defined (SPARK) 34 | #include "application.h" 35 | #endif 36 | 37 | // This file contains C code, and ESP32 Arduino has changed to use the C++ template version of min()/max() which we can't use with C, so we can't depend on a #define min() from Arduino anymore 38 | #ifndef min 39 | #define min(a,b) ((a)<(b)?(a):(b)) 40 | #endif 41 | 42 | #include "GifDecoder.h" 43 | 44 | #if GIFDEBUG == 1 45 | #define DEBUG_SCREEN_DESCRIPTOR 1 46 | #define DEBUG_GLOBAL_COLOR_TABLE 1 47 | #define DEBUG_PROCESSING_PLAIN_TEXT_EXT 1 48 | #define DEBUG_PROCESSING_GRAPHIC_CONTROL_EXT 1 49 | #define DEBUG_PROCESSING_APP_EXT 1 50 | #define DEBUG_PROCESSING_COMMENT_EXT 1 51 | #define DEBUG_PROCESSING_FILE_TERM 1 52 | #define DEBUG_PROCESSING_TABLE_IMAGE_DESC 1 53 | #define DEBUG_PROCESSING_TBI_DESC_START 1 54 | #define DEBUG_PROCESSING_TBI_DESC_INTERLACED 1 55 | #define DEBUG_PROCESSING_TBI_DESC_LOCAL_COLOR_TABLE 1 56 | #define DEBUG_PROCESSING_TBI_DESC_LZWCODESIZE 1 57 | #define DEBUG_PROCESSING_TBI_DESC_DATABLOCKSIZE 1 58 | #define DEBUG_PROCESSING_TBI_DESC_LZWIMAGEDATA_OVERFLOW 1 59 | #define DEBUG_PROCESSING_TBI_DESC_LZWIMAGEDATA_SIZE 1 60 | #define DEBUG_PARSING_DATA 1 61 | #define DEBUG_DECOMPRESS_AND_DISPLAY 1 62 | 63 | #define DEBUG_WAIT_FOR_KEY_PRESS 0 64 | 65 | #endif 66 | 67 | // Error codes 68 | #define ERROR_NONE 0 69 | #define ERROR_DONE_PARSING 1 70 | #define ERROR_WAITING 2 71 | #define ERROR_FILEOPEN -1 72 | #define ERROR_FILENOTGIF -2 73 | #define ERROR_BADGIFFORMAT -3 74 | #define ERROR_UNKNOWNCONTROLEXT -4 75 | 76 | #define GIFHDRTAGNORM "GIF87a" // tag in valid GIF file 77 | #define GIFHDRTAGNORM1 "GIF89a" // tag in valid GIF file 78 | #define GIFHDRSIZE 6 79 | 80 | // Global GIF specific definitions 81 | #define COLORTBLFLAG 0x80 82 | #define INTERLACEFLAG 0x40 83 | #define TRANSPARENTFLAG 0x01 84 | 85 | #define NO_TRANSPARENT_INDEX -1 86 | 87 | // Disposal methods 88 | #define DISPOSAL_NONE 0 89 | #define DISPOSAL_LEAVE 1 90 | #define DISPOSAL_BACKGROUND 2 91 | #define DISPOSAL_RESTORE 3 92 | 93 | 94 | 95 | template 96 | void GifDecoder::setStartDrawingCallback(callback f) { 97 | startDrawingCallback = f; 98 | } 99 | 100 | template 101 | void GifDecoder::setUpdateScreenCallback(callback f) { 102 | updateScreenCallback = f; 103 | } 104 | 105 | template 106 | void GifDecoder::setDrawPixelCallback(pixel_callback f) { 107 | drawPixelCallback = f; 108 | } 109 | 110 | template 111 | void GifDecoder::setScreenClearCallback(callback f) { 112 | screenClearCallback = f; 113 | } 114 | 115 | template 116 | void GifDecoder::setFileSeekCallback(file_seek_callback f) { 117 | fileSeekCallback = f; 118 | } 119 | 120 | template 121 | void GifDecoder::setFilePositionCallback(file_position_callback f) { 122 | filePositionCallback = f; 123 | } 124 | 125 | template 126 | void GifDecoder::setFileReadCallback(file_read_callback f) { 127 | fileReadCallback = f; 128 | } 129 | 130 | template 131 | void GifDecoder::setFileReadBlockCallback(file_read_block_callback f) { 132 | fileReadBlockCallback = f; 133 | } 134 | 135 | // Backup the read stream by n bytes 136 | template 137 | void GifDecoder::backUpStream(int n) { 138 | fileSeekCallback(filePositionCallback() - n); 139 | } 140 | 141 | // Read a file byte 142 | template 143 | int GifDecoder::readByte() { 144 | 145 | int b = fileReadCallback(); 146 | if (b == -1) { 147 | #if GIFDEBUG == 1 148 | DSERIAL.println("Read error or EOF occurred"); 149 | #endif 150 | } 151 | return b; 152 | } 153 | 154 | // Read a file word 155 | template 156 | int GifDecoder::readWord() { 157 | 158 | int b0 = readByte(); 159 | int b1 = readByte(); 160 | return (b1 << 8) | b0; 161 | } 162 | 163 | // Read the specified number of bytes into the specified buffer 164 | template 165 | int GifDecoder::readIntoBuffer(void *buffer, int numberOfBytes) { 166 | 167 | int result = fileReadBlockCallback(buffer, numberOfBytes); 168 | // if (result == -1) { 169 | // DSERIAL.println("Read error or EOF occurred"); 170 | // } 171 | return result; 172 | } 173 | 174 | // Fill a portion of imageData buffer with a color index 175 | template 176 | void GifDecoder::fillImageDataRect(uint8_t colorIndex, int x, int y, int width, int height) { 177 | 178 | int yOffset; 179 | 180 | for (int yy = y; yy < height + y; yy++) { 181 | yOffset = yy * maxGifWidth; 182 | for (int xx = x; xx < width + x; xx++) { 183 | imageData[yOffset + xx] = colorIndex; 184 | } 185 | } 186 | } 187 | 188 | // Fill entire imageData buffer with a color index 189 | template 190 | void GifDecoder::fillImageData(uint8_t colorIndex) { 191 | 192 | memset(imageData, colorIndex, sizeof(imageData)); 193 | } 194 | 195 | // Copy image data in rect from a src to a dst 196 | template 197 | void GifDecoder::copyImageDataRect(uint8_t *dst, uint8_t *src, int x, int y, int width, int height) { 198 | 199 | int yOffset, offset; 200 | 201 | for (int yy = y; yy < height + y; yy++) { 202 | yOffset = yy * maxGifWidth; 203 | for (int xx = x; xx < width + x; xx++) { 204 | offset = yOffset + xx; 205 | dst[offset] = src[offset]; 206 | } 207 | } 208 | } 209 | 210 | // Make sure the file is a Gif file 211 | template 212 | bool GifDecoder::parseGifHeader() { 213 | 214 | char buffer[10]; 215 | 216 | readIntoBuffer(buffer, GIFHDRSIZE); 217 | if ((strncmp(buffer, GIFHDRTAGNORM, GIFHDRSIZE) != 0) && 218 | (strncmp(buffer, GIFHDRTAGNORM1, GIFHDRSIZE) != 0)) { 219 | return false; 220 | } 221 | else { 222 | return true; 223 | } 224 | } 225 | 226 | // Parse the logical screen descriptor 227 | template 228 | void GifDecoder::parseLogicalScreenDescriptor() { 229 | 230 | lsdWidth = readWord(); 231 | lsdHeight = readWord(); 232 | lsdPackedField = readByte(); 233 | lsdBackgroundIndex = readByte(); 234 | lsdAspectRatio = readByte(); 235 | 236 | #if GIFDEBUG == 1 && DEBUG_SCREEN_DESCRIPTOR == 1 237 | DSERIAL.print("lsdWidth: "); 238 | DSERIAL.println(lsdWidth); 239 | DSERIAL.print("lsdHeight: "); 240 | DSERIAL.println(lsdHeight); 241 | DSERIAL.print("lsdPackedField: "); 242 | DSERIAL.println(lsdPackedField, HEX); 243 | DSERIAL.print("lsdBackgroundIndex: "); 244 | DSERIAL.println(lsdBackgroundIndex); 245 | DSERIAL.print("lsdAspectRatio: "); 246 | DSERIAL.println(lsdAspectRatio); 247 | #endif 248 | } 249 | 250 | // Parse the global color table 251 | template 252 | void GifDecoder::parseGlobalColorTable() { 253 | 254 | // Does a global color table exist? 255 | if (lsdPackedField & COLORTBLFLAG) { 256 | 257 | // A GCT was present determine how many colors it contains 258 | colorCount = 1 << ((lsdPackedField & 7) + 1); 259 | 260 | #if GIFDEBUG == 1 && DEBUG_GLOBAL_COLOR_TABLE == 1 261 | DSERIAL.print("Global color table with "); 262 | DSERIAL.print(colorCount); 263 | DSERIAL.println(" colors present"); 264 | #endif 265 | // Read color values into the palette array 266 | int colorTableBytes = sizeof(rgb_24) * colorCount; 267 | readIntoBuffer(palette, colorTableBytes); 268 | } 269 | } 270 | 271 | // Parse plain text extension and dispose of it 272 | template 273 | void GifDecoder::parsePlainTextExtension() { 274 | 275 | #if GIFDEBUG == 1 && DEBUG_PROCESSING_PLAIN_TEXT_EXT == 1 276 | DSERIAL.println("\nProcessing Plain Text Extension"); 277 | #endif 278 | // Read plain text header length 279 | uint8_t len = readByte(); 280 | 281 | // Consume plain text header data 282 | readIntoBuffer(tempBuffer, len); 283 | 284 | // Consume the plain text data in blocks 285 | len = readByte(); 286 | while (len != 0) { 287 | readIntoBuffer(tempBuffer, len); 288 | len = readByte(); 289 | } 290 | } 291 | 292 | // Parse a graphic control extension 293 | template 294 | void GifDecoder::parseGraphicControlExtension() { 295 | 296 | #if GIFDEBUG == 1 && DEBUG_PROCESSING_GRAPHIC_CONTROL_EXT == 1 297 | DSERIAL.println("\nProcessing Graphic Control Extension"); 298 | #endif 299 | int len = readByte(); // Check length 300 | if (len != 4) { 301 | DSERIAL.println("Bad graphic control extension"); 302 | } 303 | 304 | int packedBits = readByte(); 305 | frameDelay = readWord(); 306 | transparentColorIndex = readByte(); 307 | 308 | if ((packedBits & TRANSPARENTFLAG) == 0) { 309 | // Indicate no transparent index 310 | transparentColorIndex = NO_TRANSPARENT_INDEX; 311 | } 312 | disposalMethod = (packedBits >> 2) & 7; 313 | if (disposalMethod > 3) { 314 | disposalMethod = 0; 315 | DSERIAL.println("Invalid disposal value"); 316 | } 317 | 318 | readByte(); // Toss block end 319 | 320 | #if GIFDEBUG == 1 && DEBUG_PROCESSING_GRAPHIC_CONTROL_EXT == 1 321 | DSERIAL.print("PacketBits: "); 322 | DSERIAL.println(packedBits, HEX); 323 | DSERIAL.print("Frame delay: "); 324 | DSERIAL.println(frameDelay); 325 | DSERIAL.print("transparentColorIndex: "); 326 | DSERIAL.println(transparentColorIndex); 327 | DSERIAL.print("disposalMethod: "); 328 | DSERIAL.println(disposalMethod); 329 | #endif 330 | } 331 | 332 | // Parse application extension 333 | template 334 | void GifDecoder::parseApplicationExtension() { 335 | 336 | memset(tempBuffer, 0, sizeof(tempBuffer)); 337 | 338 | #if GIFDEBUG == 1 && DEBUG_PROCESSING_APP_EXT == 1 339 | DSERIAL.println("\nProcessing Application Extension"); 340 | #endif 341 | 342 | // Read block length 343 | uint8_t len = readByte(); 344 | 345 | // Read app data 346 | readIntoBuffer(tempBuffer, len); 347 | 348 | #if GIFDEBUG == 1 && DEBUG_PROCESSING_APP_EXT == 1 349 | // Conditionally display the application extension string 350 | if (strlen(tempBuffer) != 0) { 351 | DSERIAL.print("Application Extension: "); 352 | DSERIAL.println(tempBuffer); 353 | } 354 | #endif 355 | 356 | // Consume any additional app data 357 | len = readByte(); 358 | while (len != 0) { 359 | readIntoBuffer(tempBuffer, len); 360 | len = readByte(); 361 | } 362 | } 363 | 364 | // Parse comment extension 365 | template 366 | void GifDecoder::parseCommentExtension() { 367 | 368 | #if GIFDEBUG == 1 && DEBUG_PROCESSING_COMMENT_EXT == 1 369 | DSERIAL.println("\nProcessing Comment Extension"); 370 | #endif 371 | 372 | // Read block length 373 | uint8_t len = readByte(); 374 | while (len != 0) { 375 | // Clear buffer 376 | memset(tempBuffer, 0, sizeof(tempBuffer)); 377 | 378 | // Read len bytes into buffer 379 | readIntoBuffer(tempBuffer, len); 380 | 381 | #if GIFDEBUG == 1 && DEBUG_PROCESSING_COMMENT_EXT == 1 382 | // Display the comment extension string 383 | if (strlen(tempBuffer) != 0) { 384 | DSERIAL.print("Comment Extension: "); 385 | DSERIAL.println(tempBuffer); 386 | } 387 | #endif 388 | // Read the new block length 389 | len = readByte(); 390 | } 391 | } 392 | 393 | // Parse file terminator 394 | template 395 | int GifDecoder::parseGIFFileTerminator() { 396 | 397 | #if GIFDEBUG == 1 && DEBUG_PROCESSING_FILE_TERM == 1 398 | DSERIAL.println("\nProcessing file terminator"); 399 | #endif 400 | 401 | uint8_t b = readByte(); 402 | if (b != 0x3B) { 403 | 404 | #if GIFDEBUG == 1 && DEBUG_PROCESSING_FILE_TERM == 1 405 | DSERIAL.print("Terminator byte: "); 406 | DSERIAL.println(b, HEX); 407 | #endif 408 | DSERIAL.println("Bad GIF file format - Bad terminator"); 409 | return ERROR_BADGIFFORMAT; 410 | } 411 | else { 412 | return ERROR_NONE; 413 | } 414 | } 415 | 416 | // Parse table based image data 417 | template 418 | void GifDecoder::parseTableBasedImage() { 419 | 420 | #if GIFDEBUG == 1 && DEBUG_PROCESSING_TBI_DESC_START == 1 421 | DSERIAL.println("\nProcessing Table Based Image Descriptor"); 422 | #endif 423 | 424 | #if GIFDEBUG == 1 && DEBUG_PARSING_DATA == 1 425 | DSERIAL.println("File Position: "); 426 | DSERIAL.println(filePositionCallback()); 427 | DSERIAL.println("File Size: "); 428 | //DSERIAL.println(file.size()); 429 | #endif 430 | 431 | // Parse image descriptor 432 | tbiImageX = readWord(); 433 | tbiImageY = readWord(); 434 | tbiWidth = readWord(); 435 | tbiHeight = readWord(); 436 | tbiPackedBits = readByte(); 437 | 438 | #if GIFDEBUG == 1 439 | DSERIAL.print("tbiImageX: "); 440 | DSERIAL.println(tbiImageX); 441 | DSERIAL.print("tbiImageY: "); 442 | DSERIAL.println(tbiImageY); 443 | DSERIAL.print("tbiWidth: "); 444 | DSERIAL.println(tbiWidth); 445 | DSERIAL.print("tbiHeight: "); 446 | DSERIAL.println(tbiHeight); 447 | DSERIAL.print("PackedBits: "); 448 | DSERIAL.println(tbiPackedBits, HEX); 449 | #endif 450 | 451 | // Is this image interlaced ? 452 | tbiInterlaced = ((tbiPackedBits & INTERLACEFLAG) != 0); 453 | 454 | #if GIFDEBUG == 1 && DEBUG_PROCESSING_TBI_DESC_INTERLACED == 1 455 | DSERIAL.print("Image interlaced: "); 456 | DSERIAL.println((tbiInterlaced != 0) ? "Yes" : "No"); 457 | #endif 458 | 459 | // Does this image have a local color table ? 460 | bool localColorTable = ((tbiPackedBits & COLORTBLFLAG) != 0); 461 | 462 | if (localColorTable) { 463 | int colorBits = ((tbiPackedBits & 7) + 1); 464 | colorCount = 1 << colorBits; 465 | 466 | #if GIFDEBUG == 1 && DEBUG_PROCESSING_TBI_DESC_LOCAL_COLOR_TABLE == 1 467 | DSERIAL.print("Local color table with "); 468 | DSERIAL.print(colorCount); 469 | DSERIAL.println(" colors present"); 470 | #endif 471 | // Read colors into palette 472 | int colorTableBytes = sizeof(rgb_24) * colorCount; 473 | readIntoBuffer(palette, colorTableBytes); 474 | } 475 | 476 | // One time initialization of imageData before first frame 477 | if (keyFrame) { 478 | if (transparentColorIndex == NO_TRANSPARENT_INDEX) { 479 | fillImageData(lsdBackgroundIndex); 480 | } 481 | else { 482 | fillImageData(transparentColorIndex); 483 | } 484 | keyFrame = false; 485 | 486 | rectX = 0; 487 | rectY = 0; 488 | rectWidth = maxGifWidth; 489 | rectHeight = maxGifHeight; 490 | } 491 | // Don't clear matrix screen for these disposal methods 492 | if ((prevDisposalMethod != DISPOSAL_NONE) && (prevDisposalMethod != DISPOSAL_LEAVE)) { 493 | if(screenClearCallback) 494 | (*screenClearCallback)(); 495 | } 496 | 497 | // Process previous disposal method 498 | if (prevDisposalMethod == DISPOSAL_BACKGROUND) { 499 | // Fill portion of imageData with previous background color 500 | fillImageDataRect(prevBackgroundIndex, rectX, rectY, rectWidth, rectHeight); 501 | } 502 | else if (prevDisposalMethod == DISPOSAL_RESTORE) { 503 | copyImageDataRect(imageData, imageDataBU, rectX, rectY, rectWidth, rectHeight); 504 | } 505 | 506 | // Save disposal method for this frame for next time 507 | prevDisposalMethod = disposalMethod; 508 | 509 | if (disposalMethod != DISPOSAL_NONE) { 510 | // Save dimensions of this frame 511 | rectX = tbiImageX; 512 | rectY = tbiImageY; 513 | rectWidth = tbiWidth; 514 | rectHeight = tbiHeight; 515 | 516 | // limit rectangle to the bounds of maxGifWidth*maxGifHeight 517 | if(rectX + rectWidth > maxGifWidth) 518 | rectWidth = maxGifWidth-rectX; 519 | if(rectY + rectHeight > maxGifHeight) 520 | rectHeight = maxGifHeight-rectY; 521 | if(rectX >= maxGifWidth || rectY >= maxGifHeight) { 522 | rectX = rectY = rectWidth = rectHeight = 0; 523 | } 524 | 525 | if (disposalMethod == DISPOSAL_BACKGROUND) { 526 | if (transparentColorIndex != NO_TRANSPARENT_INDEX) { 527 | prevBackgroundIndex = transparentColorIndex; 528 | } 529 | else { 530 | prevBackgroundIndex = lsdBackgroundIndex; 531 | } 532 | } 533 | else if (disposalMethod == DISPOSAL_RESTORE) { 534 | copyImageDataRect(imageDataBU, imageData, rectX, rectY, rectWidth, rectHeight); 535 | } 536 | } 537 | 538 | // Read the min LZW code size 539 | lzwCodeSize = readByte(); 540 | 541 | #if GIFDEBUG == 1 && DEBUG_PROCESSING_TBI_DESC_LZWCODESIZE == 1 542 | DSERIAL.print("LzwCodeSize: "); 543 | DSERIAL.println(lzwCodeSize); 544 | DSERIAL.println("File Position Before: "); 545 | DSERIAL.println(filePositionCallback()); 546 | #endif 547 | 548 | unsigned long filePositionBefore = filePositionCallback(); 549 | 550 | // Gather the lzw image data 551 | // NOTE: the dataBlockSize byte is left in the data as the lzw decoder needs it 552 | int offset = 0; 553 | int dataBlockSize = readByte(); 554 | while (dataBlockSize != 0) { 555 | #if GIFDEBUG == 1 && DEBUG_PROCESSING_TBI_DESC_DATABLOCKSIZE == 1 556 | DSERIAL.print("dataBlockSize: "); 557 | DDSERIAL.println(dataBlockSize); 558 | #endif 559 | backUpStream(1); 560 | dataBlockSize++; 561 | fileSeekCallback(filePositionCallback() + dataBlockSize); 562 | 563 | offset += dataBlockSize; 564 | dataBlockSize = readByte(); 565 | } 566 | 567 | #if GIFDEBUG == 1 && DEBUG_PROCESSING_TBI_DESC_LZWIMAGEDATA_SIZE == 1 568 | DSERIAL.print("total lzwImageData Size: "); 569 | DSERIAL.println(offset); 570 | DSERIAL.println("File Position Test: "); 571 | DSERIAL.println(filePositionCallback()); 572 | #endif 573 | 574 | // this is the position where GIF decoding needs to pick up after decompressing frame 575 | unsigned long filePositionAfter = filePositionCallback(); 576 | 577 | fileSeekCallback(filePositionBefore); 578 | 579 | // Process the animation frame for display 580 | 581 | // Initialize the LZW decoder for this frame 582 | lzw_decode_init(lzwCodeSize); 583 | lzw_setTempBuffer((uint8_t*)tempBuffer); 584 | 585 | // Make sure there is at least some delay between frames 586 | if (frameDelay < 1) { 587 | frameDelay = 1; 588 | } 589 | 590 | // Decompress LZW data and display the frame 591 | decompressAndDisplayFrame(filePositionAfter); 592 | 593 | // Graphic control extension is for a single frame 594 | transparentColorIndex = NO_TRANSPARENT_INDEX; 595 | disposalMethod = DISPOSAL_NONE; 596 | } 597 | 598 | // Parse gif data 599 | template 600 | int GifDecoder::parseData() { 601 | if(nextFrameTime_ms > millis()) 602 | return ERROR_WAITING; 603 | 604 | #if GIFDEBUG == 1 && DEBUG_PARSING_DATA == 1 605 | DSERIAL.println("\nParsing Data Block"); 606 | #endif 607 | 608 | bool parsedFrame = false; 609 | while (!parsedFrame) { 610 | 611 | #if GIFDEBUG == 1 && DEBUG_WAIT_FOR_KEY_PRESS == 1 612 | DSERIAL.println("\nPress Key For Next"); 613 | while(DSERIAL.read() <= 0); 614 | #endif 615 | 616 | // Determine what kind of data to process 617 | uint8_t b = readByte(); 618 | 619 | if (b == 0x2c) { 620 | // Parse table based image 621 | #if GIFDEBUG == 1 && DEBUG_PARSING_DATA == 1 622 | DSERIAL.println("\nParsing Table Based"); 623 | #endif 624 | parseTableBasedImage(); 625 | parsedFrame = true; 626 | 627 | } 628 | else if (b == 0x21) { 629 | // Parse extension 630 | b = readByte(); 631 | 632 | #if GIFDEBUG == 1 && DEBUG_PARSING_DATA == 1 633 | DSERIAL.println("\nParsing Extension"); 634 | #endif 635 | 636 | // Determine which kind of extension to parse 637 | switch (b) { 638 | case 0x01: 639 | // Plain test extension 640 | parsePlainTextExtension(); 641 | break; 642 | case 0xf9: 643 | // Graphic control extension 644 | parseGraphicControlExtension(); 645 | break; 646 | case 0xfe: 647 | // Comment extension 648 | parseCommentExtension(); 649 | break; 650 | case 0xff: 651 | // Application extension 652 | parseApplicationExtension(); 653 | break; 654 | default: 655 | DSERIAL.print("Unknown control extension: "); 656 | DSERIAL.println(b, HEX); 657 | return ERROR_UNKNOWNCONTROLEXT; 658 | } 659 | } 660 | else { 661 | #if GIFDEBUG == 1 && DEBUG_PARSING_DATA == 1 662 | DSERIAL.println("\nParsing Done"); 663 | #endif 664 | 665 | // Push unprocessed byte back into the stream for later processing 666 | backUpStream(1); 667 | 668 | return ERROR_DONE_PARSING; 669 | } 670 | } 671 | return ERROR_NONE; 672 | } 673 | 674 | template 675 | int GifDecoder::startDecoding(void) { 676 | // Initialize variables 677 | keyFrame = true; 678 | prevDisposalMethod = DISPOSAL_NONE; 679 | transparentColorIndex = NO_TRANSPARENT_INDEX; 680 | nextFrameTime_ms = 0; 681 | fileSeekCallback(0); 682 | 683 | // Validate the header 684 | if (! parseGifHeader()) { 685 | DSERIAL.println("Not a GIF file"); 686 | return ERROR_FILENOTGIF; 687 | } 688 | // If we get here we have a gif file to process 689 | 690 | // Parse the logical screen descriptor 691 | parseLogicalScreenDescriptor(); 692 | 693 | // Parse the global color table 694 | parseGlobalColorTable(); 695 | 696 | return ERROR_NONE; 697 | } 698 | 699 | template 700 | int GifDecoder::decodeFrame(void) { 701 | // Parse gif data 702 | int result = parseData(); 703 | if (result < ERROR_NONE) { 704 | DSERIAL.println("Error: "); 705 | DSERIAL.println(result); 706 | DSERIAL.println(" occurred during parsing of data"); 707 | return result; 708 | } 709 | 710 | if (result == ERROR_DONE_PARSING) { 711 | //startDecoding(); 712 | // Initialize variables like with a new file 713 | keyFrame = true; 714 | prevDisposalMethod = DISPOSAL_NONE; 715 | transparentColorIndex = NO_TRANSPARENT_INDEX; 716 | nextFrameTime_ms = 0; 717 | fileSeekCallback(0); 718 | 719 | // parse Gif Header like with a new file 720 | parseGifHeader(); 721 | 722 | // Parse the logical screen descriptor 723 | parseLogicalScreenDescriptor(); 724 | 725 | // Parse the global color table 726 | parseGlobalColorTable(); 727 | } 728 | 729 | return result; 730 | } 731 | 732 | // Decompress LZW data and display animation frame 733 | template 734 | void GifDecoder::decompressAndDisplayFrame(unsigned long filePositionAfter) { 735 | 736 | // Each pixel of image is 8 bits and is an index into the palette 737 | 738 | // How the image is decoded depends upon whether it is interlaced or not 739 | // Decode the interlaced LZW data into the image buffer 740 | if (tbiInterlaced) { 741 | // Decode every 8th line starting at line 0 742 | for (int line = tbiImageY + 0; line < tbiHeight + tbiImageY; line += 8) { 743 | lzw_decode(imageData + (line * maxGifWidth) + tbiImageX, tbiWidth, min(imageData + (line * maxGifWidth) + maxGifWidth, imageData + sizeof(imageData))); 744 | } 745 | // Decode every 8th line starting at line 4 746 | for (int line = tbiImageY + 4; line < tbiHeight + tbiImageY; line += 8) { 747 | lzw_decode(imageData + (line * maxGifWidth) + tbiImageX, tbiWidth, min(imageData + (line * maxGifWidth) + maxGifWidth, imageData + sizeof(imageData))); 748 | } 749 | // Decode every 4th line starting at line 2 750 | for (int line = tbiImageY + 2; line < tbiHeight + tbiImageY; line += 4) { 751 | lzw_decode(imageData + (line * maxGifWidth) + tbiImageX, tbiWidth, min(imageData + (line * maxGifWidth) + maxGifWidth, imageData + sizeof(imageData))); 752 | } 753 | // Decode every 2nd line starting at line 1 754 | for (int line = tbiImageY + 1; line < tbiHeight + tbiImageY; line += 2) { 755 | lzw_decode(imageData + (line * maxGifWidth) + tbiImageX, tbiWidth, min(imageData + (line * maxGifWidth) + maxGifWidth, imageData + sizeof(imageData))); 756 | } 757 | } 758 | else { 759 | // Decode the non interlaced LZW data into the image data buffer 760 | for (int line = tbiImageY; line < tbiHeight + tbiImageY; line++) { 761 | lzw_decode(imageData + (line * maxGifWidth) + tbiImageX, tbiWidth, imageData + sizeof(imageData)); 762 | } 763 | } 764 | 765 | #if GIFDEBUG == 1 && DEBUG_DECOMPRESS_AND_DISPLAY == 1 766 | DSERIAL.println("File Position After: "); 767 | DSERIAL.println(filePositionCallback()); 768 | #endif 769 | 770 | #if GIFDEBUG == 1 && DEBUG_WAIT_FOR_KEY_PRESS == 1 771 | DSERIAL.println("\nPress Key For Next"); 772 | while(DSERIAL.read() <= 0); 773 | #endif 774 | 775 | // LZW doesn't parse through all the data, manually set position 776 | fileSeekCallback(filePositionAfter); 777 | 778 | // Optional callback can be used to get drawing routines ready 779 | if(startDrawingCallback) 780 | (*startDrawingCallback)(); 781 | 782 | // Image data is decompressed, now display portion of image affected by frame 783 | int yOffset, pixel; 784 | for (int y = tbiImageY; y < tbiHeight + tbiImageY; y++) { 785 | yOffset = y * maxGifWidth; 786 | for (int x = tbiImageX; x < tbiWidth + tbiImageX; x++) { 787 | // Get the next pixel 788 | pixel = imageData[yOffset + x]; 789 | 790 | // Check pixel transparency 791 | if (pixel == transparentColorIndex) { 792 | continue; 793 | } 794 | 795 | // Pixel not transparent so get color from palette and draw the pixel 796 | if(drawPixelCallback) 797 | (*drawPixelCallback)(x, y, palette[pixel].red, palette[pixel].green, palette[pixel].blue); 798 | } 799 | } 800 | // Make animation frame visible 801 | // swapBuffers() call can take up to 1/framerate seconds to return (it waits until a buffer copy is complete) 802 | // note the time before calling 803 | 804 | // wait until time to display next frame 805 | while(nextFrameTime_ms > millis()); 806 | 807 | // calculate time to display next frame 808 | nextFrameTime_ms = millis() + (10 * frameDelay); 809 | if(updateScreenCallback) 810 | (*updateScreenCallback)(); 811 | } 812 | -------------------------------------------------------------------------------- /zmodem_rz.cpp: -------------------------------------------------------------------------------- 1 | /*% cc -compat -M2 -Ox -K -i -DMD -DOMEN % -o rz; size rz; 2 | <-xtx-*> cc386 -Ox -DMD -DOMEN -DSEGMENTS=8 rz.c -o $B/rz; size $B/rz 3 | * 4 | * rz.c By Chuck Forsberg 5 | * 6 | * cc -O rz.c -o rz USG (3.0) Unix 7 | * cc -O -DV7 rz.c -o rz Unix V7, BSD 2.8 - 4.3 8 | * 9 | * ln rz rb; ln rz rx For either system 10 | * 11 | * ln rz /usr/bin/rzrmail For remote mail. Make this the 12 | * login shell. rzrmail then calls 13 | * rmail(1) to deliver mail. 14 | * 15 | * To compile on VMS: 16 | * 17 | * define LNK$LIBRARY SYS$LIBRARY:VAXCRTL.OLB 18 | * cc rz.c 19 | * cc vvmodem.c 20 | * link rz,vvmodem 21 | * rz :== $disk:[username.subdir]rz.exe 22 | * 23 | * 24 | * Unix is a trademark of Western Electric Company 25 | * 26 | * A program for Unix to receive files and commands from computers running 27 | * Professional-YAM, PowerCom, YAM, IMP, or programs supporting XMODEM. 28 | * rz uses Unix buffered input to reduce wasted CPU time. 29 | * 30 | * Iff the program is invoked by rzCOMMAND, output is piped to 31 | * "COMMAND filename" (Unix only) 32 | * 33 | * Some systems (Venix, Coherent, Regulus) may not support tty raw mode 34 | * read(2) the same way as Unix. ONEREAD must be defined to force one 35 | * character reads for these systems. Added 7-01-84 CAF 36 | * 37 | * Alarm signal handling changed to work with 4.2 BSD 7-15-84 CAF 38 | * 39 | * BIX added 6-30-87 to support BIX(TM) upload protocol used by the 40 | * Byte Information Exchange. 41 | * 42 | * NFGVMIN Updated 2-18-87 CAF for Xenix systems where c_cc[VMIN] 43 | * doesn't work properly (even though it compiles without error!), 44 | * 45 | * SEGMENTS=n added 2-21-88 as a model for CP/M programs 46 | * for CP/M-80 systems that cannot overlap modem and disk I/O. 47 | * 48 | * VMS flavor hacks begin with rz version 2.00 49 | * 50 | * -DMD may be added to compiler command line to compile in 51 | * Directory-creating routines from Public Domain TAR by John Gilmore 52 | * 53 | * HOWMANY may be tuned for best performance 54 | * 55 | * USG UNIX (3.0) ioctl conventions courtesy Jeff Martin 56 | */ 57 | 58 | #include "zmodem_config.h" 59 | #include "zmodem_fixes.h" 60 | 61 | #ifdef ARDUINO_SMALL_MEMORY_INCLUDE_RZ 62 | 63 | #include 64 | 65 | #define xsendline(c) sendline(c) 66 | 67 | #include "zmodem.h" 68 | #include "zmodem_zm.h" 69 | #include "zmodem_crc16.cpp" 70 | 71 | _PROTOTYPE(long getfree , (void)); 72 | _PROTOTYPE(int wcreceive , (int argc , char **argp )); 73 | _PROTOTYPE(int wcrxpn , (char *rpn )); 74 | _PROTOTYPE(int wcrx , ()); 75 | _PROTOTYPE(int wcgetsec , (char *rxbuf , int maxtime )); 76 | //_PROTOTYPE(int readline , (int timeout )); 77 | _PROTOTYPE(void purgeline , (void)); 78 | _PROTOTYPE(int procheader , (char *name )); 79 | _PROTOTYPE(int putsec , (char *buf , int n )); 80 | //_PROTOTYPE(void sendline , (int c )); 81 | _PROTOTYPE(void flushmo , (void)); 82 | _PROTOTYPE(void uncaps , (char *s )); 83 | _PROTOTYPE(int IsAnyLower , (char *s )); 84 | 85 | // Pete (El Supremo) 86 | //void zperr(); 87 | 88 | 89 | _PROTOTYPE(void canit , (void)); 90 | _PROTOTYPE(void report , (int sct )); 91 | _PROTOTYPE(int checkpath , (char *name )); 92 | _PROTOTYPE(int tryz , (void)); 93 | _PROTOTYPE(int rzfiles , (void)); 94 | _PROTOTYPE(int rzfile , (void)); 95 | _PROTOTYPE(void zmputs , (char *s )); 96 | _PROTOTYPE(int closeit , (void)); 97 | _PROTOTYPE(void ackbibi , (void)); 98 | _PROTOTYPE(void bttyout , (int c )); 99 | //_PROTOTYPE(int sys2 , (char *s )); 100 | //_PROTOTYPE(void exec2 , (char *s )); 101 | 102 | /* 103 | * Max value for HOWMANY is 255. 104 | * A larger value reduces system overhead but may evoke kernel bugs. 105 | * 133 corresponds to an XMODEM/CRC sector 106 | */ 107 | #ifndef HOWMANY 108 | #define HOWMANY 133 109 | #endif 110 | 111 | 112 | 113 | #define WCEOT (-10) 114 | #ifdef ARDUINO_SMALL_MEMORY 115 | #define PATHLEN 12 116 | #else 117 | #define PATHLEN 256 /* ready for 4.2 bsd ? */ 118 | #endif 119 | #define UNIXFILE 0xF000 /* The S_IFMT file mask bit for stat */ 120 | 121 | 122 | 123 | 124 | 125 | #ifndef ARDUINO 126 | #ifdef vax11c 127 | #include "vrzsz.c" /* most of the system dependent stuff here */ 128 | #else 129 | #include "rbsb.c" /* most of the system dependent stuff here */ 130 | #endif 131 | 132 | #include "crctab.c" 133 | #endif 134 | 135 | #ifndef ARDUINO 136 | FILEd *fout; 137 | #else 138 | extern SdFile fout; 139 | #endif 140 | 141 | // Dylan (monte_carlo_ecm, bitflipper, etc.) - Moved this to a global variable to enable 142 | // crash recovery (continuation of partial file transfer) 143 | extern long rxbytes; 144 | 145 | /* 146 | * Routine to calculate the free bytes on the current file system 147 | * ~0 means many free bytes (unknown) 148 | */ 149 | long getfree(void) 150 | { 151 | return(~0L); /* many free bytes ... */ 152 | } 153 | 154 | 155 | 156 | 157 | 158 | //#ifdef ONEREAD 159 | /* Sorry, Regulus and some others don't work right in raw mode! */ 160 | //int Readnum = 1; /* Number of bytes to ask for in read() from modem */ 161 | //#else 162 | //int Readnum = HOWMANY; /* Number of bytes to ask for in read() from modem */ 163 | //#endif 164 | 165 | #define DEFBYTL 2000000000L /* default rx file size */ 166 | extern long Bytesleft; /* number of bytes of incoming file left */ 167 | //long Modtime; /* Unix style mod time for incoming file */ 168 | //int Filemode; /* Unix style mode for incoming file */ 169 | 170 | // Dylan (monte_carlo_ecm, bitflipper, etc.) - Once again, in order to save every precious byte I 171 | // could find, I borrow the tail end of the receive buffer for the incoming Pathname. By the time 172 | // the ZModem file transfer begins and actually uses bytes of the buffer up to this point (768), 173 | // the need for Pathname is past because the file is already open. Again, very unorthodox, I 174 | // don't like writing code like this, but it's either tricks like this or the sketch cannot work 175 | // at all on a board with 2K of RAM. 176 | 177 | //char Pathname[PATHLEN]; 178 | #define Pathname (&oneKbuf[768]) 179 | 180 | //char *Progname; /* the name by which we were called */ 181 | 182 | #define Batch 0 183 | #define Topipe 0 184 | #define MakeLCPathname TRUE /* make received pathname lower case */ 185 | 186 | 187 | //int Nflag = 0; /* Don't really transfer files */ 188 | #define Rxclob FALSE /* Clobber existing file */ 189 | #define Rxbinary FALSE /* receive all files in bin mode */ 190 | #define Rxascii FALSE /* receive files in ascii (translate) mode */ 191 | uint8_t Thisbinary; /* current file is to be received in bin mode */ 192 | extern int Blklen; /* record length of received packets */ 193 | 194 | #ifdef SEGMENTS 195 | int chinseg = 0; /* Number of characters received in this data seg */ 196 | char secbuf[1+(SEGMENTS+1)*1024]; 197 | #else 198 | #define secbuf oneKbuf 199 | #define SECBUF_LEN TXBSIZE 200 | #endif 201 | 202 | 203 | //char linbuf[HOWMANY]; 204 | uint8_t Lleft=0; /* number of characters in linbuf */ 205 | //time_t timep[2]; 206 | 207 | 208 | 209 | 210 | //PEL 211 | //jmp_buf tohere; /* For the interrupt on RX timeout */ 212 | 213 | 214 | 215 | uint8_t tryzhdrtype=ZRINIT; /* Header type to send corresponding to Last rx close */ 216 | 217 | #ifdef ARDUINO_RECV 218 | 219 | #ifndef ARDUINO 220 | /* called by signal interrupt or terminate to clean things up */ 221 | void bibi(int n) 222 | { 223 | if (Zmodem) 224 | zmputs(Attn); 225 | canit(); 226 | mode(0); 227 | fprintf(stderr, "rz: caught signal %d; exiting\n", n); 228 | cucheck(); 229 | exit(128+n); 230 | } 231 | #endif 232 | 233 | 234 | /* 235 | * Debugging information output interface routine 236 | */ 237 | /* VARARGS1 */ 238 | /*void vfile(char *f, char *a, char *b, char *c) 239 | { 240 | if (Verbose > 2) { 241 | fprintf(stderr, f, a, b, c); 242 | fprintf(stderr, "\n"); 243 | } 244 | }*/ 245 | 246 | /* 247 | * Let's receive something already. 248 | */ 249 | 250 | #define rbmsg F("%s ready. To begin transfer, type \"%s file ...\" to your modem program\r\n\n") 251 | 252 | int wcreceive(int argc, char **argp) 253 | //int argc; 254 | //char **argp; 255 | { 256 | register c; 257 | 258 | if (Batch || argc==0) { 259 | Crcflg=1; 260 | if ( !Quiet) 261 | fprintf(stderr, rbmsg, Progname, Nozmodem?"sb":"sz"); 262 | if (c=tryz()) { 263 | if (c == ZCOMPL) { 264 | fout.close(); 265 | return OK; 266 | } 267 | if (c == ERROR) { 268 | //DSERIAL.println(F("fubar 1")); 269 | goto fubar; 270 | } 271 | c = rzfiles(); 272 | if (c) { 273 | //DSERIAL.println(F("fubar 2")); 274 | goto fubar; 275 | } 276 | } 277 | else { 278 | for (;;) { 279 | if (wcrxpn(secbuf)== ERROR) { 280 | //DSERIAL.println(F("fubar 3")); 281 | goto fubar; 282 | } 283 | if (secbuf[0]==0) 284 | return OK; 285 | if (procheader(secbuf) == ERROR) { 286 | //DSERIAL.println(F("fubar 4")); 287 | goto fubar; 288 | } 289 | if (wcrx()==ERROR) { 290 | //DSERIAL.println(F("fubar 5")); 291 | goto fubar; 292 | } 293 | } 294 | } 295 | } 296 | else { 297 | Bytesleft = DEFBYTL; 298 | //Filemode = 0; 299 | //Modtime = 0L; 300 | 301 | procheader(""); 302 | strcpy(Pathname, *argp); 303 | // if (checkpath(Pathname)) { 304 | // canit(); 305 | // return ERROR; 306 | // } 307 | //DSERIAL.print("rz: ready to receive "); 308 | //DSERIAL.println(Pathname); 309 | #ifndef ARDUINO 310 | if ((fout=fopen(Pathname, "w")) == NULL) 311 | #else 312 | if (!fout.open(Pathname, O_WRITE | O_CREAT | O_AT_END)) 313 | #endif 314 | return ERROR; 315 | rxbytes = fout.fileSize(); 316 | 317 | if (wcrx()==ERROR) { 318 | DSERIAL.println(F("fubar 6")); 319 | goto fubar; 320 | } 321 | } 322 | fout.close(); 323 | return OK; 324 | fubar: 325 | canit(); 326 | #ifndef ARDUINO 327 | #ifndef vax11c 328 | if (Topipe && fout) { 329 | pclose(fout); 330 | return ERROR; 331 | } 332 | #endif 333 | if (fout) 334 | fclose(fout); 335 | #ifndef vax11c 336 | if (Restricted) { 337 | unlink(Pathname); 338 | fprintf(stderr, F("\r\nrz: %s removed.\r\n"), Pathname); 339 | } 340 | #endif 341 | #else 342 | fout.flush(); 343 | fout.sync(); 344 | fout.close(); 345 | #endif 346 | return ERROR; 347 | } 348 | 349 | int Firstsec; 350 | /* 351 | * Fetch a pathname from the other end as a C ctyle ASCIZ string. 352 | * Length is indeterminate as long as less than Blklen 353 | * A null string represents no more files (YMODEM) 354 | */ 355 | int wcrxpn(char *rpn) 356 | /* receive a pathname */ 357 | { 358 | register c; 359 | 360 | #ifdef NFGVMIN 361 | readline(1); 362 | #else 363 | purgeline(); 364 | #endif 365 | 366 | et_tu: 367 | Firstsec=TRUE; 368 | Eofseen=FALSE; 369 | sendline(Crcflg?WANTCRC:NAK); 370 | Lleft=0; /* Do read next time ... */ 371 | while ((c = wcgetsec(rpn, 100)) != 0) { 372 | if (c == WCEOT) { 373 | zperr( "Pathname fetch returned %d", c); 374 | sendline(ACK); 375 | Lleft=0; /* Do read next time ... */ 376 | readline(1); 377 | goto et_tu; 378 | } 379 | return ERROR; 380 | 381 | 382 | } 383 | sendline(ACK); 384 | return OK; 385 | } 386 | 387 | /* 388 | * Adapted from CMODEM13.C, written by 389 | * Jack M. Wierda and Roderick W. Hart 390 | */ 391 | 392 | int wcrx() 393 | { 394 | int sectnum, sectcurr; 395 | char sendchar; 396 | int cblklen; /* bytes to dump this block */ 397 | 398 | Firstsec=TRUE; 399 | sectnum=0; 400 | Eofseen=FALSE; 401 | sendchar=Crcflg?WANTCRC:NAK; 402 | 403 | for (;;) { 404 | sendline(sendchar); /* send it now, we're ready! */ 405 | Lleft=0; /* Do read next time ... */ 406 | sectcurr=wcgetsec(secbuf, (sectnum&0177)?50:130); 407 | report(sectcurr); 408 | if (sectcurr==((sectnum+1) &0377)) { 409 | sectnum++; 410 | cblklen = Bytesleft>Blklen ? Blklen:Bytesleft; 411 | if (putsec(secbuf, cblklen)==ERROR) 412 | return ERROR; 413 | if ((Bytesleft-=cblklen) < 0) 414 | Bytesleft = 0; 415 | sendchar=ACK; 416 | } 417 | else if (sectcurr==(sectnum&0377)) { 418 | zperr( "Received dup Sector"); 419 | sendchar=ACK; 420 | } 421 | else if (sectcurr==WCEOT) { 422 | if (closeit()) 423 | return ERROR; 424 | sendline(ACK); 425 | Lleft=0; /* Do read next time ... */ 426 | return OK; 427 | } 428 | else if (sectcurr==ERROR) 429 | return ERROR; 430 | else { 431 | zperr( "Sync Error"); 432 | return ERROR; 433 | } 434 | } 435 | } 436 | 437 | /* 438 | * Wcgetsec fetches a Ward Christensen type sector. 439 | * Returns sector number encountered or ERROR if valid sector not received, 440 | * or CAN CAN received 441 | * or WCEOT if eot sector 442 | * time is timeout for first char, set to 4 seconds thereafter 443 | ***************** NO ACK IS SENT IF SECTOR IS RECEIVED OK ************** 444 | * (Caller must do that when he is good and ready to get next sector) 445 | */ 446 | 447 | int wcgetsec(char *rxbuf,int maxtime) 448 | { 449 | int checksum, wcj, firstch; 450 | unsigned short oldcrc; 451 | char *p; 452 | int sectcurr; 453 | 454 | for (Lastrx=errors=0; errors=0; ) { 467 | if ((firstch=readline(1)) < 0) { 468 | //DSERIAL.println(F("bilge 1")); 469 | goto bilge; 470 | } 471 | oldcrc=updcrc(firstch, oldcrc); 472 | checksum += (*p++ = firstch); 473 | } 474 | if ((firstch=readline(1)) < 0) { 475 | //DSERIAL.println(F("bilge 2")); 476 | 477 | goto bilge; 478 | } 479 | if (Crcflg) { 480 | oldcrc=updcrc(firstch, oldcrc); 481 | if ((firstch=readline(1)) < 0) { 482 | //DSERIAL.println(F("bilge 3")); 483 | 484 | goto bilge; 485 | } 486 | oldcrc=updcrc(firstch, oldcrc); 487 | if (oldcrc & 0xFFFF) { 488 | zperr( "CRC"); 489 | } else { 490 | Firstsec=FALSE; 491 | return sectcurr; 492 | } 493 | } 494 | else if (((checksum-firstch)&0377)==0) { 495 | Firstsec=FALSE; 496 | return sectcurr; 497 | } 498 | else 499 | zperr( "Checksum"); 500 | } 501 | else 502 | zperr("Sector number garbled"); 503 | } 504 | /* make sure eot really is eot and not just mixmash */ 505 | #ifdef NFGVMIN 506 | else if (firstch==EOT && readline(1)==TIMEOUT) 507 | return WCEOT; 508 | #else 509 | else if (firstch==EOT && Lleft==0) 510 | return WCEOT; 511 | #endif 512 | else if (firstch==CAN) { 513 | if (Lastrx==CAN) { 514 | zperr( "Sender CANcelled"); 515 | return ERROR; 516 | } 517 | else { 518 | Lastrx=CAN; 519 | continue; 520 | } 521 | } 522 | else if (firstch==TIMEOUT) { 523 | if (Firstsec) 524 | goto humbug; 525 | bilge: 526 | zperr( "TIMEOUT"); 527 | } 528 | else 529 | zperr( "Got 0%o sector header", firstch); 530 | 531 | humbug: 532 | Lastrx=0; 533 | while(readline(1)!=TIMEOUT) 534 | ; 535 | if (Firstsec) { 536 | sendline(Crcflg?WANTCRC:NAK); 537 | Lleft=0; /* Do read next time ... */ 538 | } 539 | else { 540 | maxtime=40; 541 | sendline(NAK); 542 | Lleft=0; /* Do read next time ... */ 543 | } 544 | } 545 | /* try to stop the bubble machine. */ 546 | canit(); 547 | return ERROR; 548 | } 549 | #endif 550 | 551 | 552 | #ifndef NOTDEF 553 | 554 | /* 555 | * Process incoming file information header 556 | */ 557 | int procheader(char *name) 558 | { 559 | char *openmode, *p; 560 | 561 | /* set default parameters and overrides */ 562 | openmode = "w"; 563 | Thisbinary = (!Rxascii) || Rxbinary; 564 | if (Lzmanag) 565 | zmanag = Lzmanag; 566 | 567 | /* 568 | * Process ZMODEM remote file management requests 569 | */ 570 | if (!Rxbinary && zconv == ZCNL) /* Remote ASCII override */ 571 | Thisbinary = 0; 572 | if (zconv == ZCBIN) /* Remote Binary override */ 573 | Thisbinary = TRUE; 574 | else if (zmanag == ZMAPND) 575 | openmode = "a"; 576 | 577 | #ifndef BIX 578 | /* Check for existing file */ 579 | #ifndef ARDUINO 580 | if (!Rxclob && (zmanag&ZMMASK) != ZMCLOB && (fout=fopen(name, "r"))) { 581 | fclose(fout); 582 | return ERROR; 583 | } 584 | #else 585 | // if (!Rxclob && (zmanag&ZMMASK) != ZMCLOB && sd.exists(name)) { 586 | // return ERROR; 587 | // } 588 | #endif 589 | #endif 590 | 591 | Bytesleft = DEFBYTL; 592 | //Filemode = 0; 593 | //Modtime = 0L; 594 | 595 | p = name + 1 + strlen(name); 596 | if (*p) { /* file coming from Unix or DOS system */ 597 | // sscanf(p, "%ld%lo%o", &Bytesleft, &Modtime, &Filemode); 598 | Bytesleft = atol(p); 599 | 600 | #ifndef vax11c 601 | // if (Filemode & UNIXFILE) 602 | ++Thisbinary; 603 | #endif 604 | if (Verbose) { 605 | fprintf(stderr, "\nIncoming: %s %ld %lo %o\n", 606 | name, Bytesleft, Modtime, Filemode); 607 | } 608 | } 609 | 610 | #ifdef BIX 611 | if ((fout=fopen(F("scratchpad"), openmode)) == NULL) 612 | return ERROR; 613 | return OK; 614 | #else 615 | 616 | else { /* File coming from CP/M system */ 617 | for (p=name; *p; ++p) /* change / to _ */ 618 | if ( *p == '/') 619 | *p = '_'; 620 | 621 | if ( *--p == '.') /* zap trailing period */ 622 | *p = 0; 623 | } 624 | 625 | #ifndef vax11c 626 | /* if (!Zmodem && MakeLCPathname && !IsAnyLower(name) 627 | && !(Filemode&UNIXFILE)) 628 | uncaps(name); */ 629 | #endif 630 | 631 | strcpy(Pathname, name); 632 | if (Verbose) { 633 | fprintf(stderr, "Receiving %s %s %s\n", 634 | name, Thisbinary?"BIN":"ASCII", openmode); 635 | } 636 | // if (checkpath(name)) { 637 | // canit(); 638 | // return ERROR; 639 | // } 640 | // if (Nflag) 641 | // name = "/dev/null"; 642 | #ifndef vax11c 643 | #ifdef OMEN 644 | if (name[0] == '!' || name[0] == '|') { 645 | if ( !(fout = popen(name+1, "w"))) { 646 | return ERROR; 647 | } 648 | Topipe = -1; 649 | return(OK); 650 | } 651 | #endif 652 | #endif 653 | 654 | #ifndef ARDUINO 655 | fout = fopen(name, openmode); 656 | #else 657 | fout.open(name, O_WRITE | O_CREAT | O_AT_END); 658 | rxbytes = fout.fileSize(); 659 | 660 | #endif 661 | #ifndef ARDUINO 662 | if ( !fout) 663 | #else 664 | if (!fout.isOpen()) 665 | #endif 666 | return ERROR; 667 | // } 668 | return OK; 669 | #endif /* BIX */ 670 | } 671 | 672 | /* 673 | * Putsec writes the n characters of buf to receive file fout. 674 | * If not in binary mode, carriage returns, and all characters 675 | * starting with CPMEOF are discarded. 676 | */ 677 | int putsec(char *buf,int n) 678 | { 679 | if (n == 0) 680 | return OK; 681 | if (Thisbinary) { 682 | #ifndef ARDUINO 683 | for (char *p=buf; --n>=0; ) 684 | putc( *p++, fout); 685 | #else 686 | fout.write(buf, n); 687 | // for (p=buf; --n>=0; ) 688 | // fout.write(*p++); 689 | 690 | #endif 691 | } 692 | else { 693 | if (Eofseen) 694 | return OK; 695 | for (char *p=buf; --n>=0; ++p ) { 696 | if ( *p == '\r') 697 | continue; 698 | if (*p == CPMEOF) { 699 | Eofseen=TRUE; 700 | return OK; 701 | } 702 | #ifndef ARDUINO 703 | putc(*p ,fout); 704 | #else 705 | fout.write(*p); 706 | #endif 707 | } 708 | } 709 | return OK; 710 | } 711 | #endif 712 | 713 | 714 | /* make string s lower case */ 715 | /*void uncaps(char *s) 716 | { 717 | for ( ; *s; ++s) 718 | if (isupper(*s)) 719 | *s = tolower(*s); 720 | } */ 721 | /* 722 | * IsAnyLower returns TRUE if string s has lower case letters. 723 | */ 724 | int IsAnyLower(char *s) 725 | { 726 | for ( ; *s; ++s) 727 | if (islower(*s)) 728 | return TRUE; 729 | return FALSE; 730 | } 731 | 732 | 733 | void report(int sct) 734 | { 735 | if (Verbose>1) 736 | fprintf(stderr,"%03d%c",sct,sct%10? ' ' : '\r'); 737 | } 738 | 739 | 740 | /* 741 | * Initialize for Zmodem receive attempt, try to activate Zmodem sender 742 | * Handles ZSINIT frame 743 | * Return ZFILE if Zmodem filename received, -1 on error, 744 | * ZCOMPL if transaction finished, else 0 745 | */ 746 | int tryz(void) 747 | { 748 | int c, n; 749 | int cmdzack1flg; 750 | 751 | //DSERIAL.println(F("Entering tryz")); 752 | 753 | if (Nozmodem) /* Check for "rb" program name */ 754 | return 0; 755 | 756 | tryzhdrtype=ZRINIT; 757 | 758 | for (n=Zmodem?15:5; --n>=0; ) { 759 | /* Set buffer length (0) and capability flags */ 760 | #ifdef SEGMENTS 761 | stohdr(SEGMENTS*1024L); 762 | #else 763 | #ifndef ARDUINO 764 | stohdr(0L); 765 | #else 766 | // This unfortunate piece is a limiting factor. This parameter is supposed to control 767 | // the maximum buffer size that the sender expects us to have. It seems most ZModem send implementations 768 | // ignore it, with Hyperterminal being the only exception I can find. Without setting this, even 769 | // Hyperterminal outstrips the Arduino's speed and buffer (64 bytes) at 57600 baud. 770 | stohdr(SECBUF_LEN); 771 | 772 | #endif 773 | 774 | #endif 775 | #ifdef CANBREAK 776 | Txhdr[ZF0] = CANFC32|CANFDX|CANOVIO|CANBRK; 777 | #else 778 | Txhdr[ZF0] = CANFC32|CANFDX|CANOVIO; 779 | #endif 780 | 781 | if (Zctlesc) 782 | Txhdr[ZF0] |= TESCCTL; 783 | zshhdr(tryzhdrtype, Txhdr); 784 | if (tryzhdrtype == ZSKIP) /* Don't skip too far */ 785 | tryzhdrtype = ZRINIT; /* CAF 8-21-87 */ 786 | again: 787 | switch (zgethdr(Rxhdr, 0)) { 788 | case ZRQINIT: 789 | //DSERIAL.println(F("tryz got ZRQINIT")); 790 | continue; 791 | case ZEOF: 792 | //DSERIAL.println(F("tryz got ZEOF")); 793 | continue; 794 | case TIMEOUT: 795 | //DSERIAL.println(F("tryz got TIMEOUT")); 796 | continue; 797 | case ZFILE: 798 | //DSERIAL.println(F("tryz got ZFILE")); 799 | 800 | zconv = Rxhdr[ZF0]; 801 | zmanag = Rxhdr[ZF1]; 802 | ztrans = Rxhdr[ZF2]; 803 | tryzhdrtype = ZRINIT; 804 | c = zrdata(secbuf, SECBUF_LEN); 805 | mode(3); 806 | if (c == GOTCRCW) 807 | return ZFILE; 808 | zshhdr(ZNAK, Txhdr); 809 | goto again; 810 | case ZSINIT: 811 | //DSERIAL.println(F("tryz got ZSINIT")); 812 | 813 | Zctlesc = TESCCTL & Rxhdr[ZF0]; 814 | if (zrdata(Attn, ZATTNLEN) == GOTCRCW) { 815 | stohdr(1L); 816 | zshhdr(ZACK, Txhdr); 817 | goto again; 818 | } 819 | zshhdr(ZNAK, Txhdr); 820 | goto again; 821 | case ZFREECNT: 822 | stohdr(getfree()); 823 | zshhdr(ZACK, Txhdr); 824 | goto again; 825 | case ZCOMMAND: 826 | #ifdef vax11c 827 | return ERROR; 828 | #else 829 | //DSERIAL.println(F("tryz got ZCOMMAND")); 830 | 831 | cmdzack1flg = Rxhdr[ZF0]; 832 | if (zrdata(secbuf, SECBUF_LEN) == GOTCRCW) { 833 | // if (cmdzack1flg & ZCACK1) 834 | stohdr(0L); 835 | // else 836 | // stohdr((long)sys2(secbuf)); 837 | purgeline(); /* dump impatient questions */ 838 | do { 839 | zshhdr(ZCOMPL, Txhdr); 840 | } 841 | while (++errors<20 && zgethdr(Rxhdr,1) != ZFIN); 842 | ackbibi(); 843 | // if (cmdzack1flg & ZCACK1) 844 | // exec2(secbuf); 845 | return ZCOMPL; 846 | } 847 | zshhdr(ZNAK, Txhdr); 848 | goto again; 849 | #endif 850 | case ZCOMPL: 851 | //DSERIAL.println(F("tryz got ZCOMPL")); 852 | 853 | goto again; 854 | default: 855 | //DSERIAL.println(F("tryz got default")); 856 | 857 | continue; 858 | case ZFIN: 859 | //DSERIAL.println(F("tryz got ZFIN")); 860 | 861 | ackbibi(); 862 | return ZCOMPL; 863 | case ZCAN: 864 | //DSERIAL.println(F("tryz got ZCAN")); 865 | 866 | return ERROR; 867 | } 868 | } 869 | return 0; 870 | } 871 | 872 | /* 873 | * Receive 1 or more files with ZMODEM protocol 874 | */ 875 | int rzfiles(void) 876 | { 877 | int c; 878 | 879 | for (;;) { 880 | switch (c = rzfile()) { 881 | case ZEOF: 882 | case ZSKIP: 883 | switch (tryz()) { 884 | case ZCOMPL: 885 | return OK; 886 | default: 887 | return ERROR; 888 | case ZFILE: 889 | break; 890 | } 891 | continue; 892 | default: 893 | return c; 894 | case ERROR: 895 | return ERROR; 896 | } 897 | } 898 | } 899 | 900 | /* 901 | * Receive a file with ZMODEM protocol 902 | * Assumes file name frame is in secbuf 903 | */ 904 | int rzfile(void) 905 | { 906 | int c, n; 907 | // long rxbytes; 908 | 909 | Eofseen=FALSE; 910 | if (procheader(secbuf) == ERROR) { 911 | return (tryzhdrtype = ZSKIP); 912 | } 913 | 914 | n = 20; 915 | // rxbytes = 0l; 916 | 917 | for (;;) { 918 | #ifdef SEGMENTS 919 | chinseg = 0; 920 | #endif 921 | stohdr(rxbytes); 922 | zshhdr(ZRPOS, Txhdr); 923 | nxthdr: 924 | switch (c = zgethdr(Rxhdr, 0)) { 925 | default: 926 | vfile(F("rzfile: zgethdr returned %d"), c); 927 | return ERROR; 928 | case ZNAK: 929 | case TIMEOUT: 930 | #ifdef SEGMENTS 931 | putsec(secbuf, chinseg); 932 | chinseg = 0; 933 | #endif 934 | if ( --n < 0) { 935 | vfile(F("rzfile: zgethdr returned %d"), c); 936 | return ERROR; 937 | } 938 | case ZFILE: 939 | zrdata(secbuf, SECBUF_LEN); 940 | continue; 941 | case ZEOF: 942 | #ifdef SEGMENTS 943 | putsec(secbuf, chinseg); 944 | chinseg = 0; 945 | #endif 946 | if (rclhdr(Rxhdr) != rxbytes) { 947 | /* 948 | * Ignore eof if it's at wrong place - force 949 | * a timeout because the eof might have gone 950 | * out before we sent our zrpos. 951 | */ 952 | errors = 0; 953 | goto nxthdr; 954 | } 955 | if (closeit()) { 956 | tryzhdrtype = ZFERR; 957 | vfile(F("rzfile: closeit returned <> 0")); 958 | return ERROR; 959 | } 960 | vfile(F("rzfile: normal EOF")); 961 | return c; 962 | case ERROR: /* Too much garbage in header search error */ 963 | #ifdef SEGMENTS 964 | putsec(secbuf, chinseg); 965 | chinseg = 0; 966 | #endif 967 | if ( --n < 0) { 968 | vfile(F("rzfile: zgethdr returned %d"), c); 969 | return ERROR; 970 | } 971 | zmputs(Attn); 972 | continue; 973 | case ZSKIP: 974 | #ifdef SEGMENTS 975 | putsec(secbuf, chinseg); 976 | chinseg = 0; 977 | #endif 978 | closeit(); 979 | vfile(F("rzfile: Sender SKIPPED file")); 980 | return c; 981 | case ZDATA: 982 | if (rclhdr(Rxhdr) != rxbytes) { 983 | if ( --n < 0) { 984 | return ERROR; 985 | } 986 | #ifdef SEGMENTS 987 | putsec(secbuf, chinseg); 988 | chinseg = 0; 989 | #endif 990 | zmputs(Attn); 991 | continue; 992 | } 993 | moredata: 994 | if (Verbose>1) 995 | fprintf(stderr, "\r%7ld ZMODEM%s ", 996 | rxbytes, Crc32?" CRC-32":""); 997 | #ifdef SEGMENTS 998 | if (chinseg >= (1024 * SEGMENTS)) { 999 | putsec(secbuf, chinseg); 1000 | chinseg = 0; 1001 | } 1002 | switch (c = zrdata(secbuf+chinseg, 1024)) 1003 | #else 1004 | switch (c = zrdata(secbuf, SECBUF_LEN)) 1005 | #endif 1006 | { 1007 | case ZCAN: 1008 | #ifdef SEGMENTS 1009 | putsec(secbuf, chinseg); 1010 | chinseg = 0; 1011 | #endif 1012 | vfile(F("rzfile: zgethdr returned %d"), c); 1013 | return ERROR; 1014 | case ERROR: /* CRC error */ 1015 | #ifdef SEGMENTS 1016 | putsec(secbuf, chinseg); 1017 | chinseg = 0; 1018 | #endif 1019 | if ( --n < 0) { 1020 | vfile(F("rzfile: zgethdr returned %d"), c); 1021 | return ERROR; 1022 | } 1023 | zmputs(Attn); 1024 | continue; 1025 | case TIMEOUT: 1026 | #ifdef SEGMENTS 1027 | putsec(secbuf, chinseg); 1028 | chinseg = 0; 1029 | #endif 1030 | if ( --n < 0) { 1031 | vfile(F("rzfile: zgethdr returned %d"), c); 1032 | return ERROR; 1033 | } 1034 | continue; 1035 | case GOTCRCW: 1036 | n = 20; 1037 | #ifdef SEGMENTS 1038 | chinseg += Rxcount; 1039 | putsec(secbuf, chinseg); 1040 | chinseg = 0; 1041 | #else 1042 | putsec(secbuf, Rxcount); 1043 | #endif 1044 | rxbytes += Rxcount; 1045 | stohdr(rxbytes); 1046 | zshhdr(ZACK, Txhdr); 1047 | sendline(XON); 1048 | goto nxthdr; 1049 | case GOTCRCQ: 1050 | n = 20; 1051 | #ifdef SEGMENTS 1052 | chinseg += Rxcount; 1053 | #else 1054 | putsec(secbuf, Rxcount); 1055 | #endif 1056 | rxbytes += Rxcount; 1057 | stohdr(rxbytes); 1058 | zshhdr(ZACK, Txhdr); 1059 | goto moredata; 1060 | case GOTCRCG: 1061 | n = 20; 1062 | #ifdef SEGMENTS 1063 | chinseg += Rxcount; 1064 | #else 1065 | putsec(secbuf, Rxcount); 1066 | #endif 1067 | rxbytes += Rxcount; 1068 | goto moredata; 1069 | case GOTCRCE: 1070 | n = 20; 1071 | #ifdef SEGMENTS 1072 | chinseg += Rxcount; 1073 | #else 1074 | putsec(secbuf, Rxcount); 1075 | #endif 1076 | rxbytes += Rxcount; 1077 | goto nxthdr; 1078 | } 1079 | } 1080 | } 1081 | } 1082 | 1083 | /* 1084 | * Send a string to the modem, processing for \336 (sleep 1 sec) 1085 | * and \335 (break signal) 1086 | */ 1087 | void zmputs(char *s) 1088 | { 1089 | int c; 1090 | 1091 | while (*s) { 1092 | switch (c = *s++) { 1093 | case '\336': 1094 | #ifndef ARDUINO 1095 | sleep(1); 1096 | #else 1097 | delay(1000); 1098 | #endif 1099 | continue; 1100 | case '\335': 1101 | sendbrk(); 1102 | continue; 1103 | default: 1104 | sendline(c); 1105 | } 1106 | } 1107 | } 1108 | 1109 | /* 1110 | * Close the receive dataset, return OK or ERROR 1111 | */ 1112 | 1113 | int closeit(void) 1114 | { 1115 | fout.flush(); 1116 | fout.sync(); 1117 | fout.close(); 1118 | 1119 | return OK; 1120 | } 1121 | 1122 | /* 1123 | * Ack a ZFIN packet, let byegones be byegones 1124 | */ 1125 | void ackbibi(void) 1126 | { 1127 | int n; 1128 | 1129 | vfile(F("ackbibi:")); 1130 | //Readnum = 1; 1131 | stohdr(0L); 1132 | for (n=3; --n>=0; ) { 1133 | purgeline(); 1134 | zshhdr(ZFIN, Txhdr); 1135 | switch (readline(100)) { 1136 | case 'O': 1137 | readline(1); /* Discard 2nd 'O' */ 1138 | vfile(F("ackbibi complete")); 1139 | return; 1140 | case RCDO: 1141 | return; 1142 | case TIMEOUT: 1143 | default: 1144 | break; 1145 | } 1146 | } 1147 | } 1148 | 1149 | /* End of rz.c */ 1150 | 1151 | 1152 | 1153 | 1154 | #endif 1155 | --------------------------------------------------------------------------------