diff --git a/FileLib.ino b/FileLib.ino new file mode 100644 index 0000000..f6eca99 --- /dev/null +++ b/FileLib.ino @@ -0,0 +1,368 @@ +#include "FileLib.h" + +FileLibClass::FileLibClass(StorageType type) : _type(type) {} + +FileLibClass::~FileLibClass() +{ + _printMessage("Unmounting file system\n"); + if (_type == StorageType::FL_SPIFFS) + { + LITTLEFS.end(); + } + else if (_type == StorageType::FL_MMC) + { + SD_MMC.end(); + } + else if (_type == StorageType::FL_SD) + { + SD.end(); + } +} + +bool FileLibClass::begin(uint8_t csPin, uint8_t misoPin, uint8_t mosiPin, uint8_t sckPin) +{ + _printMessage("Initializing file system\n"); + if (_type == StorageType::FL_SPIFFS) + { + _filesystem = &LITTLEFS; + return LITTLEFS.begin(true); + } + + if (_type == StorageType::FL_MMC) + { + _filesystem = &SD_MMC; + return SD_MMC.begin(); + } + + if (_type == StorageType::FL_SD) + { + _spi.begin(sckPin, misoPin, mosiPin, csPin); + _filesystem = &SD; + return SD.begin(5, _spi); + } + + return false; +} + +File FileLibClass::openForWriting(const char *fileName) +{ + _printMessage("Opening file '%s' for writing\n", fileName); + return _filesystem->open(fileName, FILE_WRITE); +} + +File FileLibClass::open(const char *fileName, const char *mode) +{ + if (!_filesystem) + { + _printMessage("File system not initialized"); + return File(); // Return an empty File object if filesystem is not initialized + } + return _filesystem->open(fileName, mode); +} + +bool FileLibClass::readPartial(const char *fileName, uint32_t startPos, uint8_t *buffer, uint16_t bufferSize) +{ + if (!_filesystem) + { + _printMessage("File system not initialized"); + return false; + } + + File file = _filesystem->open(fileName, FILE_READ); + if (!file) + { + _printMessage("Failed to open file"); + return false; + } + + if (!file.seek(startPos)) + { + _printMessage("Failed to seek to position %u in file", startPos); + file.close(); + return false; + } + + size_t bytesRead = file.read(buffer, bufferSize); + file.close(); + + if (bytesRead == 0) + { + _printMessage("No data read from file"); + return false; + } + + return true; +} + +bool FileLibClass::write(const char *fileName, const uint8_t *data, size_t dataSize, bool append) +{ + _printMessage("Writing %d bytes to file '%s'\n", dataSize, fileName); + File file = _filesystem->open(fileName, append ? FILE_APPEND : FILE_WRITE); + + // Sanity check + if (!file || file.isDirectory()) + { + _printMessage("Failed to open file '%s' for writing\n", fileName); + return false; + } + + // size_t written = file.write(reinterpret_cast(data), dataSize); + size_t written = 0; + for (size_t i = 0; i < dataSize; i++) + { + written += file.write(data[i]); + } + + // Confirm that the data was written + if (written == dataSize) + { + file.close(); + return true; + } + + if (written > 0) + { + _printMessage("Only wrote %d bytes of %d\n", written, dataSize); + file.close(); + return false; + } + + _printMessage("Failed to write to file '%s'\n", fileName); + file.close(); + return false; +} + +bool FileLibClass::writeString(const char *fileName, const uint8_t *data, bool append) +{ + _printMessage("Writing string to file '%s'\n", fileName); + // use strlen so we don't write the null terminator + int strLen = strlen((char *)&data); + return write(fileName, reinterpret_cast(data), strLen, append); +} + +void FileLibClass::listDir(const char *dirName, uint8_t levels) +{ + if (!_verbose) + { + return; + } + + _printMessage("Listing directory: %s\r\n", dirName); + + File root = _filesystem->open(dirName); + if (!root) + { + _printMessage("− failed to open directory"); + return; + } + + if (!root.isDirectory()) + { + _printMessage(" − not a directory"); + return; + } + + File file = root.openNextFile(); + while (file) + { + if (file.isDirectory()) + { + Serial.printf(" DIR : %s\n", file.name()); + if (levels) + { + listDir(file.name(), levels - 1); + } + } + else + { + Serial.printf(" FILE: %s", file.name()); + Serial.printf("\tSIZE: %d\n", file.size()); + } + file = root.openNextFile(); + } +} + +bool FileLibClass::read(const char *fileName, uint8_t *buffer, uint16_t bufferSize, bool clearBuffer) +{ + _printMessage("Reading file '%s'\n", fileName); + if (clearBuffer) + { + memset(buffer, 0, bufferSize); + } + + File file = _filesystem->open(fileName, FILE_READ); + + uint16_t fileSize = file.size(); + + // Sanity check + if (!file || file.isDirectory()) + { + _printMessage("Failed to open file '%s'.\n", fileName); + return false; + } + + // check if the buffer is big enough + if (bufferSize < fileSize) + { + _printMessage("Buffer size is too small for file '%s'.\n", fileName); + file.close(); + return false; + } + + if (!file.available()) + { + _printMessage("File '%s' is empty.\n", fileName); + } + + // size_t bytesRead = file.readBytes(buffer, fileSize); + // read the data byte by byte + for (size_t i = 0; i < fileSize; i++) + { + buffer[i] = file.read(); + } + + file.close(); + return true; +} + +bool FileLibClass::remove(const char *fileName) +{ + _printMessage("Removing file '%s'\n", fileName); + if (!_filesystem->exists(fileName)) + { + _printMessage("File '%s' does not exist.\n", fileName); + return false; + } + return _filesystem->remove(fileName); +} + +bool FileLibClass::rename(const char *fileName, const char *newName, bool overwrite) +{ + _printMessage("Renaming file '%s' to '%s'\n", fileName, newName); + if (_filesystem->exists(newName) && !overwrite) + { + _printMessage("File '%s' already exists.\n", newName); + return false; + } + + return _filesystem->rename(fileName, newName); +} + +bool FileLibClass::mkdir(const char *path) +{ + _printMessage("Creating directory '%s'\n", path); + + if (_filesystem->exists(path)) + { + _printMessage("Directory '%s' already exists.\n", path); + return false; + } + + return _filesystem->mkdir(path); +} + +uint64_t FileLibClass::getFreeSpace() +{ + _printMessage("Getting free space\n"); + if (_type == StorageType::FL_SPIFFS) + { + return LITTLEFS.totalBytes() - LITTLEFS.usedBytes(); + } + + if (_type == StorageType::FL_MMC) + { + uint64_t cardSize = SD_MMC.totalBytes(); + uint64_t usedSpace = SD_MMC.usedBytes(); + + if (usedSpace > cardSize) + { + _printMessage("ERROR: Used space is greater than card size \n"); + return 0; + } + + uint64_t freeSpace = cardSize - usedSpace; + return freeSpace; + } + + if (_type == StorageType::FL_SD) + { + uint64_t cardSize = SD.totalBytes(); + uint64_t usedSpace = SD.usedBytes(); + + if (usedSpace > cardSize) + { + _printMessage("Error: Used space is greater than card size \n"); + return 0; + } + + uint64_t freeSpace = cardSize - usedSpace; + return freeSpace; + } + + return 0; +} + +bool copy(File &source, File &dest) +{ + if (!source || !dest) + { + return false; + } + + int bufferSize = 512; + char buffer[bufferSize]; + + while (source.available()) + { + int bytesRead = source.readBytes(buffer, bufferSize); + int bytesWritten = dest.write((uint8_t *)&buffer, bytesRead); + + if (bytesWritten != bytesRead) + { + return false; + } + } + + dest.flush(); + + return true; +} + +void FileLibClass::_printMessage(const char *message, ...) +{ + if (_verbose) + { + // get the type name + const char *typeName; + switch (_type) + { + case FL_SPIFFS: + typeName = "FL_SPIFFS"; + break; + case FL_SD: + typeName = "FL_SD"; + break; + case FL_MMC: + typeName = "FL_MMC"; + break; + default: + typeName = "Unknown"; + break; + } + + char buffer[256]; + + va_list args; + va_start(args, message); + vsnprintf(buffer, sizeof(buffer), message, args); + va_end(args); + // switch for different storage types + Serial.printf("%s: %s", typeName, buffer); + } +} + +//Create FileLibClass::exists function +bool FileLibClass::exists(const char* fileName) +{ + return _filesystem->exists(fileName); +} diff --git a/GenericTypeDefs.h b/GenericTypeDefs.h new file mode 100644 index 0000000..d97d836 --- /dev/null +++ b/GenericTypeDefs.h @@ -0,0 +1,27 @@ +#ifndef GENERIC_TYPE_DEFS_H +#define GENERIC_TYPE_DEFS_H +#include "Arduino.h" + +typedef unsigned long dword; +typedef unsigned long uint32; + +typedef union +{ + uint32 val; + word w[2]; + byte v[4]; + struct + { + word lw; + word hw; + } wd; + struct + { + byte lb; + byte hb; + byte ub; + byte mb; + } bt; +} DWORD_VAL; + +#endif // GENERIC_TYPE_DEFS_H diff --git a/HTTPSHandler.h b/HTTPSHandler.h new file mode 100644 index 0000000..f28f2c8 --- /dev/null +++ b/HTTPSHandler.h @@ -0,0 +1,222 @@ +#ifndef HTTPS_HANDLER_H +#define HTTPS_HANDLER_H + +#include +#include "SD_MMC.h" + +/** + * @class HTTPSHandler + * + * This class provides functionality to handle HTTPS connections, including downloading, + * uploading, and sending post requests. It uses a state machine internally to track the + * status of downloads. + */ +class HTTPSHandler +{ +public: + /** + * @enum HttpsState + * + * Enumerates the possible states for the https class. + */ + enum class HttpsState + { + IDLE, // Initial state, no download in progress. + CONNECTING, // Connecting to the server. + WAITING, // Waiting for server response. + COMPLETE, // Response is complete. + ERROR // An error occurred during the download. + }; + + /** + * @enum Type of http request + */ + enum class RequestType + { + GET, + DOWNLOAD, + POST, + UPLOAD + }; + + // A callback type that notifies about download progress. + using ProgressCallback = void (*)(int bytesDownloaded, int totalBytes); + +private: + WiFiClientSecure _client; + // The server's certificate for SSL/TLS connection. Typically the root CA certificate. + const char *_serverCert; + // Must be a domain name, not an IP address. + const char *_host; + // Range between 0 and 65535. + int _port = 443; + + // Duration in seconds. + uint8_t _downloadTimeout = 90; + // Duration in seconds. + uint8_t _responseTimeout = 100; + + // Callback function to get download progress updates. + ProgressCallback _progressCallback = nullptr; + // Current state of the download. + HttpsState _currentState = HttpsState::IDLE; + // Type of http request. + RequestType _requestType; + // Boundaries for upload. + const String _BOUNDARY = "----WebKitFormBoundary7MA4YWxkTrZu0gW"; + + String _response; + + // The File object where the downloaded data will be saved. + File _destinationFile; + // The path or endpoint on the server. + const char *_path; + // A header to be sent with the request. + String _customHeader; + + /** + * Establish a connection to the server. + * @return true if connected successfully, false otherwise. + */ + bool _connect(); + + /** + * Internal method to get the content length from the server's response headers. + * @return The content length as an integer. + */ + int _getContentLength(); + + /** + * Processes the server's response for a given request. + * This method reads the available data from the client, appends it to + * the internal response string, and updates the current state + * of the HTTPSHandler based on the contents of the response. + */ + void _processResponse(); + + /** + * Process the download once initiated. + * @param dest The File object where the downloaded data will be saved. + */ + void _processDownload(File &dest); + + /** + * Get only the file name from a given path. + */ + String _extractFileName(const String &fullPath); + + /** + * Create boundary for multipart upload. + * Used in http request header. + */ + String _createBoundary(const String &fileNameToUpload); + +public: + /** + * Constructor for the HTTPSHandler class. + * @param host The server's domain or IP address. + * @param port The port to connect on the server. + * @param serverCert The server's certificate for SSL/TLS connection. + */ + HTTPSHandler(const char *serverCert); + + /** + * Destructor for the HTTPSHandler class. + */ + ~HTTPSHandler(); + + /** + * Establish a connection to the server. + * @param host The server host to connect to. + * @param port The server port to connect to. Defaults to 443. + * @param serverCert The server certificate. Defaults to nullptr. + * @return true if connected successfully, false otherwise. + */ + bool connect(const char *host, int port = 443, const char *serverCert = nullptr); + + /** + * Retrieves the server's response for the most recent request. + * + * @return A string containing the full response from the server. + */ + String getResponse() { return _response; } + + /** + * Checks if the server's response for the most recent request is complete. + * + * @return True if the response is complete, otherwise false. + */ + bool isResponseComplete() { return _currentState == HttpsState::COMPLETE; } + + /** + * Set a callback function to get download progress updates. + * @param callback A function pointer to the callback. + */ + void setProgressCallback(ProgressCallback callback) { _progressCallback = callback; } + + /** + * Get the current download state. + * @return The current state of the download. + */ + HttpsState getState() const { return _currentState; } + + /** + * Set a custom header for the next HTTP request. + * @param header The header string to set. + */ + void setCustomHeader(const String &header) { _customHeader = header; } + + /** + * Initiate the download process. + * @param path The path or endpoint on the server from where to download. + */ + void getRequest(const char *path); + + /** + * Upload a file to the server. + * @param path The path or endpoint on the server where to upload. + * @param source The File object containing the data to be uploaded. + * @param fileName The name that will be used for the uploaded file on the server. + * If not specified, the name of the source file will be used. + * @return true if the upload was successful, false otherwise. + */ + bool uploadFile(const char *path, File &source, const String &fileName = ""); + + /** + * Send a POST request to the server. + * @param path The path or endpoint on the server to send the POST request. + * @param postData The data to be sent in the POST request. + * @return true if the request was successful, false otherwise. + */ + bool postRequest(const char *path, const String &postData); + + /** + * Set the target from a given path and destination file. + * @param path The path or endpoint on the server from where to download. + * @param dest The File object where the downloaded data will be saved. + */ + void setTarget(const char *sourcePath, File &dest); + + /** + * Continue processing the state machine. + */ + void advanceStateMachine(); + + /** + * Check if an error occurred during the download. + * @return true if an error occurred, false otherwise. + */ + bool isErrorOccurred() { return _currentState == HttpsState::ERROR; } + + /** + * Close the current connection with the server. + */ + void close(); + + /** + * + */ + // void get(const String &url, Callback callback); +}; + +#endif // HTTPS_HANDLER_H diff --git a/HTTPSHandler.ino b/HTTPSHandler.ino new file mode 100644 index 0000000..e4351b5 --- /dev/null +++ b/HTTPSHandler.ino @@ -0,0 +1,367 @@ +#include "HTTPSHandler.h" + +HTTPSHandler::HTTPSHandler(const char *serverCert) +{ + _client.setCACert(serverCert); +} + +bool HTTPSHandler::connect(const char *host, int port, const char *serverCert) +{ + _host = host; + _port = port; + if (serverCert != nullptr) + { + _client.setCACert(serverCert); + } + _currentState = HttpsState::CONNECTING; + // reset response + _response = ""; + return _connect(); +} + +bool HTTPSHandler::_connect() +{ + return _client.connect(_host, _port); +} + +int HTTPSHandler::_getContentLength() +{ + unsigned long startTime = millis(); + + String line; + int contentLength = -1; + + if (!_client.connected()) + { + Serial.print("Not connected when getting content length"); + _currentState = HttpsState::ERROR; + return -1; + } + + while (contentLength == -1) + { + while (_client.available()) + { + line = _client.readStringUntil('\n'); + line.trim(); // Remove any trailing whitespace + + if (line.startsWith("Content-Length: ")) + { + contentLength = line.substring(15).toInt(); + } + + // Empty line / end of headers + if (line.length() == 0) + { + break; + } + } + + // Check for timeout + if (millis() - startTime > _responseTimeout * 1000) + { + Serial.println("Timeout while trying to get Content-Length!"); + _currentState = HttpsState::ERROR; + return -1; + } + } + + return contentLength; +} + +void HTTPSHandler::getRequest(const char *path) +{ + // Check if the client is connected before sending + if (!_client.connected()) + { + Serial.println("Error: Client is not connected!"); + _currentState = HttpsState::ERROR; + return; + } + + String request = "GET " + String(path) + " HTTP/1.1\r\n"; + request += "Host: " + String(_host) + "\r\n"; + + if (_customHeader.length() > 0) + { + request += _customHeader + "\r\n"; + } + + request += "Connection: keep-alive\r\n\r\n"; + + size_t bytesSent = _client.print(request); + + // Check if all bytes of the request were sent + if (bytesSent != request.length()) + { + Serial.println("Error: Not all bytes sent!"); + _currentState = HttpsState::ERROR; + return; + } + + _currentState = HttpsState::WAITING; + _requestType = RequestType::GET; +} + +void HTTPSHandler::_processDownload(File &dest) +{ + const uint32_t BUFFER_SIZE = 2048; + int contentLength = _getContentLength(); + static int bytesRead = 0; // Number of bytes downloaded so far + byte buffer[BUFFER_SIZE]; + unsigned long startMillis = millis(); + + if (contentLength <= 0) + { + _currentState = HttpsState::ERROR; + } + + while (_currentState == HttpsState::WAITING) + { + // Then proceed to download content + while (_client.available()) + { + // If no data is received for the duration of _timeout, it times out + if (millis() - startMillis > _downloadTimeout * 1000) + { + Serial.println("Data inactivity timeout!"); + _currentState = HttpsState::ERROR; + dest.close(); + close(); + return; + } + uint32_t bytesAvailable = _client.available(); + int bytesToRead = min(bytesAvailable, BUFFER_SIZE); + + int bytesReadFromClient = _client.read(buffer, bytesToRead); + dest.write(buffer, bytesReadFromClient); + bytesRead += bytesReadFromClient; + } + + if (_progressCallback) + { + _progressCallback(bytesRead, contentLength); + } + + if (bytesRead >= contentLength) + { + dest.close(); + _currentState = HttpsState::COMPLETE; + close(); + return; + } + } +} + +String HTTPSHandler::_extractFileName(const String &fullPath) +{ + int lastSlashIndex = fullPath.lastIndexOf('/'); + + if (lastSlashIndex == -1) + { + // No slashes found, return the full path itself. + return fullPath; + } + + // Return the substring from the last slash onwards. + return fullPath.substring(lastSlashIndex + 1); +} + +String HTTPSHandler::_createBoundary(const String &fileNameToUpload) +{ + // Build the multipart form data request + String boundary = "--" + _BOUNDARY + "\r\n"; + boundary += "Content-Disposition: form-data; name=\"fileToUpload\"; filename=\"" + fileNameToUpload + "\"\r\n"; + boundary += "Content-Type: application/json\r\n"; + boundary += "\r\n"; + return boundary; +} + +bool HTTPSHandler::uploadFile(const char *destPath, File &source, const String &fileName) +{ + String fileNameToUpload = fileName == "" ? _extractFileName(source.name()) : fileName; + Serial.println("Uploading file: " + fileNameToUpload); + + // Needed for header + String boundary = _createBoundary(fileNameToUpload); + String endBoundary = "\r\n--" + _BOUNDARY + "--\r\n"; + + // Calculate content length + int contentLength = boundary.length() + endBoundary.length() + source.size(); + + // Start sending the request + _client.println("POST " + String(destPath) + " HTTP/1.1"); + _client.println("Host: appdig.com"); + if (_customHeader.length() > 0) + { + _client.println(_customHeader); + } + _client.print("Content-Length: " + String(contentLength) + "\r\n"); + _client.println("Content-Type: multipart/form-data; boundary=" + _BOUNDARY); + _client.println(); + _client.print(boundary); + + const int CHUNK_SIZE = 1024; + const long startTime = millis(); + // Start sending body in chunks + while (source.available()) + { + // Check for timeout + if (millis() - startTime > _responseTimeout * 1000) + { + Serial.println("Timeout while trying to get Content-Length!"); + _currentState = HttpsState::ERROR; + return 0; + } + byte buffer[CHUNK_SIZE]; + int bytesRead = source.read(buffer, CHUNK_SIZE); + _client.write(buffer, bytesRead); + + if (_progressCallback) + { + _progressCallback(bytesRead, contentLength); + } + } + + _client.print(endBoundary); + + _currentState = HttpsState::COMPLETE; + // give the server some time to process the request + // update in the future to handle response from server + delay(10); + close(); + return 1; +} + +bool HTTPSHandler::postRequest(const char *destPath, const String &postData) +{ + _client.println("POST " + String(destPath) + " HTTP/1.1"); + _client.println("Host: " + String(_host)); + _client.println("Content-Length: " + String(postData.length())); + _client.println("Connection: close"); + _client.println(); + _client.println(postData); + + _currentState = HttpsState::WAITING; + _requestType = RequestType::POST; + return 1; +} + +void HTTPSHandler::_processResponse() +{ + unsigned long startMillis = millis(); + // Read available data from the client and append to _response + while (_client.available()) + { + // If no data is received for the duration of _timeout, it times out + if (millis() - startMillis > _responseTimeout * 1000) + { + Serial.println("Data inactivity timeout"); + _currentState = HttpsState::ERROR; + close(); + return; + } + + char c = _client.read(); + Serial.print(c); + _response += c; + + if (_response.endsWith("\r\n0\r\n")) + { + break; + } + } + + if (!_client.connected() || _response.endsWith("\r\n0\r\n")) + { + _currentState = HttpsState::COMPLETE; + } + close(); +} + +void HTTPSHandler::setTarget(const char *sourcePath, File &destFile) +{ + _path = sourcePath; + _destinationFile = destFile; + _currentState = HttpsState::WAITING; + getRequest(_path); + _requestType = RequestType::DOWNLOAD; +} + +void HTTPSHandler::advanceStateMachine() +{ + static int lastState = -1; + if (lastState != static_cast(_currentState)) + { + Serial.println("HTTPSHandler state changed to " + static_cast(_currentState)); + lastState = static_cast(_currentState); + } + + switch (_currentState) + { + case HttpsState::CONNECTING: + if (!_client.connected()) + { + Serial.println("Could not connect to server"); + _currentState = HttpsState::ERROR; + } + _currentState = HttpsState::WAITING; + break; + + case HttpsState::WAITING: + switch (_requestType) + { + case RequestType::DOWNLOAD: + _processDownload(_destinationFile); + break; + + default: + _processResponse(); + break; + } + break; + + case HttpsState::ERROR: + _currentState = HttpsState::IDLE; + break; + + case HttpsState::COMPLETE: + _currentState = HttpsState::IDLE; + break; + + case HttpsState::IDLE: + break; + + default: + break; + } +} + +// void HTTPSHandler::get(const String &url, Callback callback) +// { +// if (!_client.connect(_host, _port)) +// { +// Serial.println("Could not connect to server"); +// return; +// } + +// _client.println("GET / HTTP/1.1"); +// _client.println("HOST appdig.com"); +// _client.println("Connection: close"); + +// _requestStarted = 1; +// _callback = callback; +// } + +void HTTPSHandler::close() +{ + if (_client.connected()) + { + _client.stop(); + } +} + +HTTPSHandler::~HTTPSHandler() +{ + close(); +} diff --git a/IO_ADI.h b/IO_ADI.h new file mode 100644 index 0000000..12b7f1e --- /dev/null +++ b/IO_ADI.h @@ -0,0 +1,78 @@ +#ifndef IO_ADI + +#define IO_ADI 1 + +#define IOSIZE 40 +uint16_t io_array[IOSIZE]; + +#define BATTERYINPUTPIN 32 // 26 v996 +#define SPAREOUTPIN 26 // 32 v996 +// #define ADDR_PB 17 + +enum io +{ + // Inputs + RET_LIMIT, // JOG_EXTEND, //0 + EXT_LIMIT, // JOG_RETRACT, + OPEN_LIMIT, + ADDRBUTTON, // SPARE_IN2, + COMPRESSOR, // SPARE_IN3, + EN_12V_SW, // RF_KEYPAD, + RF_KEYPAD, // SPARE_IN1, + POWER_RLY, // ADDR_SET,//7 + + SPARE_IN, // GPIO 25 + SPARE_OUT, // GPIO 32 + BATT_V // GPIO 26 + +}; + +/* + +//outputs +RETRACT,//8 +EXTEND, +SPARE_RLYA, //was SCRN_DN +SPARE_RLYB, //was SCRN_UP +SPARE_OUT1, +EN_SWA, +EN_SWB, +EN_SWC,//15 + +COMPRESSOR,//16 +ADDR_LED, +HI_TRIP_LED, +CHARGE_EN, +CHARGE_MODE, +SCREEN_UP, +SCREEN_DN, +SCREEN_STP,//23 + +spare1,//24 +spare2,//25 +spare3,//26 + +//ADC +BATT,//27 +GND, +ADC4to20, +ADC0to10a, +ADC_CURRENT, +JOG_RETRACT, +JOG_EXTEND, +ADPAREADC3 //34 +}; +*/ + +// io Expander Support v125 + +// set up for 2 modules +uint8_t exRly[4]; +uint8_t exOpto[4]; +uint8_t exOptoTransition[4]; +uint16_t exParm[12]; // 0-3 are input Hz 4-11 are 4 to 20 AD + +uint8_t exIndx; +uint8_t exParmIndx; + +#endif