/** * @file main.ino * @brief Main entry point and core logic for PacoMouseCYD throttle. * @author F. CaƱada * @date 2025-2026 * @copyright https://usuaris.tinet.cat/fmco/ * * This file contains the main setup and loop functions, version history, and * overall initialization for the PacoMouseCYD throttle firmware. * * This software and associated files are a DIY project that is not intended for commercial use. * This software uses libraries with different licenses, follow all their different terms included. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. * * Sources are only provided for building and uploading to the device. * You are not allowed to modify the source code or fork/publish this project. * Commercial use is forbidden. * * -------------------------------------------------------------------------------------------------- * * Use 2.8" Cheap Yellow Display ESP32-2432S028 (CYD) * - ILI9341 driver chip (320x240) * - XPT2046 chip for touch screen * * CYD Also available in 2.4" and 3.2" (Use Resistive touch) * * Select ESP32 Dev Module in Arduino IDE * * SD Card. IMPORTANT!!!: use FAT32 SD card (max. 32GB) * * -------------------------------------------------------------------------------------------------- * * v0.1 24feb25 Start writing code * v0.2 07mar25 GUI, SD Pictures, Wifi configuration and loco throttle on Z21 working * v0.3 21mar25 Added loco list sorting and loco image selection. Added internal file system for loco data. * v0.4 19apr25 Added configuration menu screen. Corrected touch rotation for CYD 2.4". Changed translations files. Added programming CV. Added speedometer. * v0.5 02jun25 Added steam loco throttle. Adding more function icons. Added Xpressnet LAN and Loconet over TCP protocols. Added experimental identify command station for Loconet. * v0.6 08oct25 Added Loconet programming. New LocoEditor for browser on SD. * v0.7 23nov25 Corrected bugs on loconet steam direction. Added accessory panels. Added WiFi analyzer. * v0.8 15dec25 Added ECoS/CS1 protocol. Updated user defined CYDs. Changes in modal windows. * v0.9 03jan26 Added Station Run for kids. Corrected minor bugs on loconet */ // PacoMouseCYD program version #define VER_H "0" #define VER_L "9" #define VER_R "c" // #define DEBUG to enable debug messages on Serial0. Remember to set baudrate to 115200 bps in console. #define DEBUG // Libraries #include // #include "config.h" // PacoMouseCYD config file // #include "icon.h" // PacoMouseCYD icons // #include "gui.h" // PacoMouseCYD Graphic User Interface // #include "translations.h" // PacoMouseCYD languages // #include // SD Card. IMPORTANT!!!: use FAT32 SD card (max. 32GB) // #include // #include // #include // #include // Graphics and font library for ILI9341 driver chip v2.5.43 // #include "XPT2046.h" // PacoMouseCYD touch screen XPT2046 (2.8": bit bang, 2.4": SPI) // #include // #include // #include // #include "lnet.h" // PacoMouseCYD LNTCP #ifdef DEBUG char output[80]; #define DEBUG_MSG(...) snprintf(output,80, __VA_ARGS__ ); \ Serial.println(output); #else #define DEBUG_MSG(...) #endif /********************************************************* ***************** SD CARD ******************************* *********************************************************/ // bool sdDetected; // File root; // char folderSel[32]; // int dirCount = 1; // char DirAndFile[15]; // char FileName[32]; // #define FORMAT_LITTLEFS_IF_FAILED true /********************************************************* * *************** TFT ******************************* *********************************************************/ // TFT_eSPI tft = TFT_eSPI(); // Invoke library, pins defined in User_Setup.h // TFT_eSprite sprite = TFT_eSprite(&tft); // // backlight // #define LEDC_CHANNEL_0 0 // use first channel of 16 channels (started from zero) // #define LEDC_RESOLUTION 8 // use 8 (12) bit precission for LEDC timer // #define LEDC_BASE_FREQ 5000 // 5000 Hz as a LEDC base frequency // #define INACT_TIME 1200 // 2 minutes inactivity (in 100ms) -> lower bright // uint8_t backlight; // uint8_t currBacklight; // uint16_t oldNeedle; // #define USB_UP 2 // Display USB up // #define USB_DOWN 0 // Display USB down // uint8_t locationUSB; //////////////////////////////////////////////////////////// // ***** TOUCHSCREEN ***** //////////////////////////////////////////////////////////// // XPT2046_TS touchscreen(XPT2046_MOSI, XPT2046_MISO, XPT2046_CLK, XPT2046_CS); // #define SW_BOOT 0 // BOOT button used to enter touchscreen calibration window // bool bootPressed; // bool calibrationPending; // bool clickDetected = false; // pulsacion detectada en panel tactil //////////////////////////////////////////////////////////// // ***** ENCODER ***** //////////////////////////////////////////////////////////// // volatile int outA; // encoder input used by ISR // volatile int outB; // volatile int copyOutA; // volatile int copyOutB; // volatile uint32_t lastTimeEncoder; // volatile byte encoderValue; // encoder shared values (ISR & program) // volatile byte encoderMax; // volatile bool encoderChange; // volatile bool encoderNeedService; // #define ENC_DEBOUNCE 3 // byte statusSwitch; // bool switchOn; // const unsigned long timeoutButtons = 50; // temporizador antirebote // unsigned long timeButtons; //////////////////////////////////////////////////////////// // ***** EEPROM ***** //////////////////////////////////////////////////////////// // #define EEPROM_SIZE 512 // enum Settings { // EE_XMIN_H, EE_XMIN_L, EE_XMAX_H, EE_XMAX_L, EE_YMIN_H, EE_YMIN_L, EE_YMAX_H, EE_YMAX_L, EE_BACKLIGHT, EE_LANGUAGE, // EE_ADRH, EE_ADRL, EE_STOP_MODE, EE_SHUNTING, EE_ROCO, EE_LOCK, EE_SHORT, EE_USB_LOCATION, EE_CMD_STA, EE_CMD_AUTO, // EE_STA_ADRH1, EE_STA_ADRL1, EE_STA_ADRH2, EE_STA_ADRL2, EE_STA_ADRH3, EE_STA_ADRL3, EE_STA_ADRH4, EE_STA_ADRL4, // EE_STA_TRNDEF, EE_STA_TRNNUM, EE_STA_NUM, EE_STA_TIME, // EE_WIFI, // datos WiFi. (Tiene que ser el ultimo) // }; // EEPROM settings // bool eepromChanged; //////////////////////////////////////////////////////////// // ***** WiFi ***** //////////////////////////////////////////////////////////// struct { char ssid[33]; // SSID char password[65]; // Password IPAddress CS_IP; // IP uint16_t port; // Port bool serverType; // Server type byte protocol; // protocol int ok; } wifiSetting; enum typeProto {CLIENT_Z21, CLIENT_XNET, CLIENT_ECOS, CLIENT_LNET}; WiFiClient Client; WiFiUDP Udp; #define z21Port 21105 // local port to listen on command station #define XnetPort 5550 #define ECoSPort 15471 uint16_t networks; uint8_t scrSSID; enum cmdStation {CMD_DR, CMD_ULI, CMD_DIG, CMD_UNKNOW}; byte typeCmdStation; // tipo de central Loconet para funciones // RSSI RANGE #define RSSI_CEILING -40 #define RSSI_FLOOR -100 #define NEAR_CHANNEL_RSSI_ALLOW -70 #define CHANNEL_WIDTH 15 #define GRAPH_HEIGHT 188 #if (TFT_WIDTH == 240) #define GRAPH_OFFSET 0 #define GRAPH_BASELINE 222 #else #define GRAPH_OFFSET 40 #define GRAPH_BASELINE 302 #endif // Channel color mapping from channel 1 to 14 // uint16_t channel_color[] = { // COLOR_RED, COLOR_ORANGE, COLOR_YELLOW, COLOR_GREEN, COLOR_CYAN, COLOR_MAGENTA, COLOR_RED, // COLOR_ORANGE, COLOR_YELLOW, COLOR_GREEN, COLOR_CYAN, COLOR_MAGENTA, COLOR_RED, COLOR_ORANGE, // }; // uint8_t scan_count = 0; // uint8_t ap_count[14]; // int32_t max_rssi[14]; //////////////////////////////////////////////////////////// // ***** GUI ***** //////////////////////////////////////////////////////////// // uint16_t posObjStack1; // uint16_t posObjStack2; // uint16_t paramChild; // uint8_t lastLanguage; //////////////////////////////////////////////////////////// // ***** PACOMOUSE ***** //////////////////////////////////////////////////////////// // enum initResult {INIT_OK, INIT_NO_SD, INIT_NO_WIFI, INIT_NO_CONNECT}; // enum Err {NO_ERROR, ERR_OFF, ERR_STOP, ERR_SERV, ERR_WAIT, ERR_FULL, ERR_CHG_WIFI, ERR_CV, ERR_ASK_SURE}; // byte errType; // initResult initStatus; // byte csStatus = 0; // #define DEFAULT_STEPS 4 // 128 steps // byte stopMode; // bool shuntingMode; // byte shortAddress; // byte scrHour, scrMin, scrRate, scrPosTime; // byte clockHour, clockMin, clockRate; // unsigned long clockTimer, clockInterval; // Internal fast clock calculation // enum prgCV {PRG_IDLE, PRG_CV, PRG_RD_CV29, PRG_RD_CV1, PRG_RD_CV17, PRG_RD_CV18, PRG_WR_CV1, PRG_WR_CV17, PRG_WR_CV18, PRG_WR_CV29}; // unsigned int CVaddress, CVdata, CVmax; // programacion CV // bool modeProg; // bool enterCVdata; // bool progFinished; // byte progStepCV, lastCV; // byte cv29, cv17, cv18; // unsigned int decoAddress; // enum lock {LOCK_SEL_LOCO, LOCK_TURNOUT, LOCK_PROG}; // byte lockOptions; //////////////////////////////////////////////////////////// // ***** LOCOMOTIVES ***** //////////////////////////////////////////////////////////// // unsigned long infoTimer; // unsigned long pingTimer; // byte myLocoData; // current loco data index // typedef union { // Loco address // uint8_t adr[2]; // uint16_t address; // } lokAddr; // typedef union { // uint8_t xFunc[4]; // loco functions, F0F4, F5F12, F13F20 y F21F28 // uint32_t Bits; // long para acceder a los bits // } lokFunc; // typedef struct { // lokAddr myAddr; // lokFunc myFunc; // uint8_t myDir; // uint8_t mySpeed; // uint8_t mySteps; // uint16_t myVmax; // char myName[NAME_LNG + 1]; // uint16_t myLocoID; // ID / picture // uint8_t myFuncIcon[30]; // } lokData; // lokData locoData[LOCOS_IN_STACK]; // Loco data // uint16_t locoStack[LOCOS_IN_STACK]; // stack para ultimas locos accedidas // uint16_t sortedLocoStack[LOCOS_IN_STACK]; // lista ordenada de locomotoras // uint16_t locoImages[LOCOS_IN_STACK + 20]; // lista de imagenes en el sistema y SD // bool useID; // #define MAX_LOCO_IMAGE sizeof(locoImages) / sizeof(locoImages[0]) // uint16_t locoImageIndex; // enum locoSort {SORT_LAST, SORT_NUM_UP, SORT_NUM_DWN, SORT_NAME_UP, SORT_NAME_DWN}; // uint16_t currOrder; // struct { // unsigned int id; // stack loco data // unsigned int locoAddress; // char locoName[NAME_LNG + 1]; // } rosterList[LOCOS_IN_STACK]; //////////////////////////////////////////////////////////// // ***** ACCESSORIES ***** //////////////////////////////////////////////////////////// #define AUTO_INTERVAL 100UL // Timer interval (100ms) #define TIME_ACC_ON 2 // accessory activation time accessory (200ms) #define TIME_ACC_CDU 2 // time to wait after accessory deactivated for CDU (200ms) // enum fifo {FIFO_EMPTY, FIFO_ACC_ON, FIFO_ACC_CDU}; // uint16_t accessoryFIFO[32]; // FIFO 32 elements. Hardcoded in functions. // uint16_t lastInFIFO; // uint16_t firstOutFIFO; // uint16_t cntFIFO; // uint16_t lastAccessory; // uint8_t accessoryTimer; // uint8_t stateFIFO; // DESVIADO: ROJO - > links thrown // RECTO: VERDE + < rechts closed // enum posDesvio {NO_MOVIDO, DESVIADO, RECTO, INVALIDO}; // bool editAccessory; // uint8_t currPanel; // uint8_t currPanelAcc; // uint8_t currAccAspects; // bool accPanelChanged; // uint16_t myTurnout; // typedef enum accType {ACC_UNDEF, ACC_TURN_L, ACC_TURN_R, ACC_TRIPLE, ACC_CROSSING, ACC_DCROSS, ACC_BETRELLE, ACC_SIGNAL2, ACC_SIGNAL3, ACC_SIGNAL4, ACC_SEM2, ACC_SEM3, // ACC_PAN, ACC_TT_L, ACC_TT_R, ACC_TT_TRK, ACC_TT_TURN, ACC_LIGHT, ACC_SOUND, ACC_POWER, ACC_KEYPAD, ACC_MAX, // }; // typedef struct { // uint16_t fncIcon; // uint16_t color; // uint16_t colorOn; // } accAspect; // typedef struct { // uint8_t aspects; // uint8_t num; // accAspect icon[4]; // } accObj; // const accObj accDef[ACC_MAX] = { // { 1, 99, {{FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}}}, // ACC_UNDEF // { 2, 99, {{FNC_TURNLD_OFF, COLOR_BLACK, COLOR_RED}, {FNC_TURNLS_OFF, COLOR_BLACK, COLOR_GREEN}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}}}, // ACC_TURN_L // { 2, 99, {{FNC_TURNRD_OFF, COLOR_BLACK, COLOR_RED}, {FNC_TURNRS_OFF, COLOR_BLACK, COLOR_GREEN}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}}}, // ACC_TURN_R // { 3, 99, {{FNC_TURN3L_OFF, COLOR_BLACK, COLOR_RED}, {FNC_TURN3S_OFF, COLOR_BLACK, COLOR_GREEN}, {FNC_TURN3R_OFF, COLOR_BLACK, COLOR_RED}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}}}, // ACC_TRIPLE // { 2, 99, {{FNC_CROSD_OFF, COLOR_BLACK, COLOR_RED}, {FNC_CROSS_OFF, COLOR_BLACK, COLOR_GREEN}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}}}, // ACC_CROSSING // { 4, 4, {{FNC_DCROSSD1_OFF, COLOR_BLACK, COLOR_RED}, {FNC_DCROSSS1_OFF, COLOR_BLACK, COLOR_GREEN}, {FNC_DCROSSD2_OFF, COLOR_BLACK, COLOR_RED}, {FNC_DCROSSS2_OFF, COLOR_BLACK, COLOR_GREEN}}}, // ACC_DCROSS // { 2, 99, {{FNC_BRETELLED_OFF, COLOR_BLACK, COLOR_RED}, {FNC_BRETELLE_OFF, COLOR_BLACK, COLOR_GREEN}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}}}, // ACC_BETRELLE // { 2, 99, {{FNC_SIGRY_OFF, COLOR_BLACK, COLOR_RED}, {FNC_SIGGW_OFF, COLOR_BLACK, COLOR_GREEN}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}}}, // ACC_SIGNAL2 // { 3, 3, {{FNC_SIGRY_OFF, COLOR_BLACK, COLOR_RED}, {FNC_SIGGW_OFF, COLOR_BLACK, COLOR_GREEN}, {FNC_SIGRY_OFF, COLOR_BLACK, COLOR_YELLOW}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}}}, // ACC_SIGNAL3 // { 4, 4, {{FNC_SIGRY_OFF, COLOR_BLACK, COLOR_RED}, {FNC_SIGGW_OFF, COLOR_BLACK, COLOR_GREEN}, {FNC_SIGRY_OFF, COLOR_BLACK, COLOR_YELLOW}, {FNC_SIGGW_OFF, COLOR_BLACK, COLOR_WHITE}}}, // ACC_SIGNAL4 // { 2, 99, {{FNC_SEMR_OFF, COLOR_BLACK, COLOR_RED}, {FNC_SEMG_OFF, COLOR_BLACK, COLOR_RED}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}}}, // ACC_SEM2 // { 3, 3, {{FNC_SEMR_OFF, COLOR_BLACK, COLOR_RED}, {FNC_SEMG_OFF, COLOR_BLACK, COLOR_RED}, {FNC_SEMY_OFF, COLOR_BLACK, COLOR_RED}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}}}, // ACC_SEM3 // { 2, 99, {{FNC_PANR_OFF, COLOR_BLACK, COLOR_RED}, {FNC_PANG_OFF, COLOR_BLACK, COLOR_RED}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}}}, // ACC_PAN // { 1, 99, {{FNC_TTL_OFF, COLOR_BLACK, COLOR_RED}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}}}, // ACC_TT_L // { 1, 99, {{FNC_TTR_OFF, COLOR_BLACK, COLOR_GREEN}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}}}, // ACC_TT_R // { 1, 99, {{FNC_TTTRK_OFF, COLOR_BLACK, COLOR_BLACK}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}}}, // ACC_TT_TRK // { 1, 99, {{FNC_TTROT_OFF, COLOR_BLACK, COLOR_YELLOW}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}}}, // ACC_TT_TURN // { 2, 99, {{FNC_LIGHT_OFF, COLOR_BLACK, COLOR_LIGHTGREY}, {FNC_LIGHT_OFF, COLOR_BLACK, COLOR_YELLOW}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}}}, // ACC_LIGHT // { 2, 99, {{FNC_SOUND_OFF, COLOR_BLACK, COLOR_LIGHTGREY}, {FNC_SOUND_OFF, COLOR_BLACK, COLOR_YELLOW}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}}}, // ACC_SOUND // { 2, 99, {{FNC_POWER_OFF, COLOR_RED, COLOR_RED}, {FNC_POWER_OFF, COLOR_GREEN, COLOR_GREEN}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}}}, // ACC_POWER // { 1, 99, {{FNC_KEYPAD_OFF, COLOR_BLACK, COLOR_YELLOW}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}, {FNC_BLANK_OFF, COLOR_LIGHTGREY, COLOR_LIGHTGREY}}}, // ACC_KEYPAD // }; // typedef struct { // accType type; // uint16_t addr; // uint16_t addr2; // char accName[ACC_LNG + 1]; // uint8_t currAspect; // uint16_t activeOutput; // '3A2G 3A2R 3A1G 3A1R 2A2G 2A2R 2A1G 2A1R 1A2G 1A2R 1A1G 1A1R 0A2G 0A2R 0A1G 0A1R' // } panelElement; // const uint16_t accOutDefault[ACC_MAX] = {0x0000, 0x0021, 0x0021, 0x0521, 0x0021, 0x4521, 0x0021, 0x0021, 0x0421, 0x8421, 0x0021, 0x0421, 0x0021, 0x0002, 0x0001, 0x0001, 0x0002, 0x0021, 0x0021, 0x0021, 0x0000}; // panelElement accPanel[16]; // panelElement currAccEdit; // uint8_t savedAspect[16][16]; //////////////////////////////////////////////////////////// // ***** SPEEDOMETER ***** //////////////////////////////////////////////////////////// // uint32_t speedoStartTime; // uint32_t speedoEndTime; // uint32_t speedoSpeed; // uint16_t speedoScale; // uint16_t speedoLength; // enum speedo {SPD_WAIT, SPD_BEGIN, SPD_COUNT, SPD_ARRIVE, SPD_END}; // uint16_t speedoPhase; //////////////////////////////////////////////////////////// // ***** STEAM ***** //////////////////////////////////////////////////////////// // uint16_t oldSteamLoco; // steam loco address // uint16_t oldPressure; // 0..270 manometer needle angle // uint16_t oldSpeedSteam; // 240..305 throttle bar angle // uint16_t oldLevelBoiler; // 0..50 water level bar // uint16_t oldLevelTender; // 0..500 tender level bar // uint8_t steamDir; // 0x80: Forward, 0x00: Backward // uint16_t targetSpeedSteam; // 0..127 calculated target speed // uint16_t currentSteamSpeed; // 0..127 current speed // uint16_t steamPressure; // 0..100 boiler pressure // uint16_t waterLevelBoiler; // 0..100 boiler water // uint16_t waterLevelTender; // 0..500 tender water // bool fillTender; // Tender water filling // bool waterInjection; // Boiler water injection // bool shovelCoal; // Shoveling coal to firebox // #define STEAM_LOAD_TIME 250 // Basic Timeout for water & steam // #define STEAM_SMOKE_SLOW 3200 // Chimney smoke at slow speed // #define STEAM_SMOKE_FAST 600 // Chimney smoke at fast speed // #define STEAM_JOHNSON_NEUTRAL 3 // Johnson bar neutral position // uint32_t currentSteamTime; // uint32_t steamTimeSpeed; // uint32_t steamTimeSteam; // uint32_t steamTimeWater; // uint32_t steamTimeLoad; // uint32_t steamTimeSmoke; // uint32_t changeSteam; // uint32_t changeWater; // uint32_t changeSpeed; // uint32_t changeSmoke; // enum steamLimits {LIMIT_THROTTLE, LIMIT_JOHNSON, LIMIT_PRESSURE, LIMIT_WATER, LIMIT_TENDER, LIMIT_BRAKE, MAX_LIMIT}; // #define LIMIT_NONE 127 // uint16_t steamSpeedLimit[MAX_LIMIT]; //////////////////////////////////////////////////////////// // ***** STATION RUN ***** //////////////////////////////////////////////////////////// // uint16_t staTime; // uint16_t staCurrTime; // uint16_t staStartTime; // uint8_t staLevel; // uint8_t staStars; // uint8_t staStations; // stations target // uint8_t staCurrStation; // stations counter // uint8_t staLastStation; // last color station // uint8_t staMaxStations; // uint8_t staMaxTurnout; // #define MAX_STATIONS 5 // const uint16_t staColors[MAX_STATIONS] = {COLOR_RED, COLOR_GREEN, COLOR_YELLOW, COLOR_WHITE, COLOR_CYAN}; // uint16_t staTurnoutAdr1; // uint16_t staTurnoutAdr2; // uint16_t staTurnoutAdr3; // uint16_t staTurnoutAdr4; // uint8_t staTurnoutDef; // bool staTurnoutPos[4]; //////////////////////////////////////////////////////////// // ***** Z21 ***** //////////////////////////////////////////////////////////// // #define csNormalOps 0x00 // Normal Operation Resumed // #define csEmergencyStop 0x01 // Emergency Stop // #define csEmergencyOff 0x02 // Emergency off. Xpressnet // #define csTrackVoltageOff 0x02 // Emergency off. Z21 // #define csStartMode 0x04 // Start mode. Xpressnet // #define csShortCircuit 0x04 // ShortCircuit // #define csServiceMode 0x08 // Service mode. Xpressnet // #define csReserved 0x10 // #define csProgrammingModeActive 0x20 // Service Mode. Z21 // #define csPowerUp 0x40 // Xpressnet // #define csErrorRAM 0x80 // Error RAM. Xpressnet // byte packetBuffer[1500]; // buffer to hold incoming packet, max. 1472 bytes // byte OutData[80]; // byte OutPos = 0; // byte OutXOR; // // Z21 white message header // #define LAN_GET_SERIAL_NUMBER 0x10 // #define LAN_GET_CODE 0x18 // #define LAN_GET_HWINFO 0x1A // #define LAN_LOGOFF 0x30 // #define LAN_X_Header 0x40 // #define LAN_SET_BROADCASTFLAGS 0x50 // #define LAN_GET_BROADCASTFLAGS 0x51 // #define LAN_GET_LOCOMODE 0x60 // #define LAN_SET_LOCOMODE 0x61 // Not implemented // #define LAN_GET_TURNOUTMODE 0x70 // #define LAN_SET_TURNOUTMODE 0x71 // Not implemented // #define LAN_RMBUS_DATACHANGED 0x80 // #define LAN_RMBUS_GETDATA 0x81 // #define LAN_RMBUS_PROGRAMMODULE 0x82 // Not implemented // #define LAN_SYSTEMSTATE_DATACHANGED 0x84 // #define LAN_SYSTEMSTATE_GETDATA 0x85 // #define LAN_RAILCOM_DATACHANGED 0x88 // #define LAN_RAILCOM_GETDATA 0x89 // #define LAN_LOCONET_Z21_RX 0xA0 // #define LAN_LOCONET_Z21_TX 0xA1 // #define LAN_LOCONET_FROM_LAN 0xA2 // #define LAN_LOCONET_DISPATCH_ADDR 0xA3 // unused // #define LAN_LOCONET_DETECTOR 0xA4 // #define LAN_FAST_CLOCK_CONTROL 0xCC // #define LAN_FAST_CLOCK_DATA 0xCD // #define LAN_FAST_CLOCK_SETTINGS_GET 0xCE // unused // #define LAN_FAST_CLOCK_SETTINGS_SET 0xCF // unused // enum xAnswer {DATA_LENL, DATA_LENH, DATA_HEADERL, DATA_HEADERH, XHEADER, DB0, DB1, DB2, DB3, DB4, DB5, DB6, DB7, DB8}; // #define Z21_PING_INTERVAL 5 // Prevent automatic LOGOFF (5s) // bool waitResultCV; //////////////////////////////////////////////////////////// // ***** XPRESSNET LAN ***** //////////////////////////////////////////////////////////// // enum xPacket {FRAME1, FRAME2, HEADER, DATA1, DATA2, DATA3, DATA4, DATA5}; // #define XNET_PING_INTERVAL 10000UL // Prevent closing the connection // volatile byte rxBufferXN[20]; // Comunicacion Xpressnet // volatile byte txBuffer[14]; // volatile byte txBytes; // volatile byte rxBytes; // byte rxXOR, rxIndice, txXOR, rxData; // byte xnetVersion, xnetCS; // bool getInfoLoco, getResultsSM; // bool askMultimaus; // Multimaus // byte highVerMM, lowVerMM; // unsigned long timeoutXnet; //////////////////////////////////////////////////////////// // ***** LOCONET OVER TCP ***** //////////////////////////////////////////////////////////// // lnMsg SendPacket; // Paquete a enviar por Loconet WiFi // lnMsg RecvPacket; // Paquete recibido por Loconet WiFi // char rcvStr[10]; // byte rcvStrPos; // enum rcvPhase {WAIT_TOKEN, RECV_TOKEN, RECV_PARAM, SENT_TOKEN, SENT_PARAM}; // byte rcvStrPhase; // bool sentOK; // unsigned long timeoutSend; // enum statusSlot {STAT_FREE, STAT_COMMON, STAT_IDLE, STAT_IN_USE}; // struct slot { // byte num; // slot number: 1..120 (0x01..0x78) // byte state; // state: in_use/idle/common/free // byte trk; // track status // } mySlot; // const byte stepsLN[] = {28, 28, 14, 128, 28, 28, 14, 128}; // #define LNET_PING_INTERVAL 55000UL // Prevent PURGE of slot (55s) // bool doDispatchGet, doDispatchPut; // dispatching // uint8_t lastTrk; // last received global track status // uint8_t autoIdentifyCS; // bool lnetProg; // programando CV o LNCV // byte ulhiProg; // Programming track off (UHLI) // enum lncv {LNCV_ART, LNCV_MOD, LNCV_ADR, LNCV_VAL}; // unsigned int artNum; // unsigned int modNum; // unsigned int numLNCV; // unsigned int valLNCV; // byte optLNCV; //////////////////////////////////////////////////////////// // ***** ECoS ***** //////////////////////////////////////////////////////////// // #define ID_ECOS 1 // Base objects ID // #define ID_PRGMANAGER 5 // #define ID_POMMANAGER 7 // #define ID_LOKMANAGER 10 // #define ID_SWMANAGER 11 // #define ID_SNIFFERMANAGER 25 // #define ID_S88MANAGER 26 // #define ID_BOOSTERMANAGER 27 // #define ID_S88FEEDBACK 100 // #define ID_INTBOOSTER 65000 // char cmd[64]; // send buffer // #define NAME_LNG 16 // loco names length // #define BUF_LNG 1024 // char inputBuffer[BUF_LNG]; // unsigned int inputLength; // #define TEXTLEN 32 // Length of symbols in input // char putBackChr; // int posFile; // // Tokens // enum { // T_NULL, T_START, T_ENDB, T_REPLY, T_EVENT, T_END, T_COMMA, T_INTLIT, T_SEMI, T_LPARENT, T_RPARENT, // T_LBRACKET, T_RBRACKET, T_STRLIT, T_IDENT, T_PRINT, T_INT, // T_GET, T_SET, T_QOBJ, T_ADDR, T_NAME, T_PROT, T_STATUS, T_STATUS2, T_STATE, // T_REQ, T_VIEW, T_CONTROL, T_RELEASE, T_FORCE, // T_GO, T_STOP, T_SHUTDWN, T_OK, T_MSG, T_SPEED, T_STEPS, T_DIR, T_FUNC, T_FSYMBOL, T_FSYMB, T_FICON, // T_LOST, T_OTHER, T_CV, T_CVOK, T_ERROR, T_SWITCH, // T_CS1, T_ECOS, T_ECOS2, T_APPV, // }; // // Token structure // struct token { // char token; // int intvalue; // }; // struct token T; // int tokenType; // char Text[TEXTLEN + 1]; // int errCode; // int idManager; // int idCommand; // int numLoks; // int lastNumValue; // bool requestedCV; // int appVer; // byte msgDecodePhase; // enum {MSG_WAIT, MSG_START, MSG_REPLY, MSG_EVENT, MSG_END, MSG_REPLYBODY, MSG_EVENTBODY}; // uint8_t mySpeedStep; // const uint8_t FunktionsTastenSymbole[] = { // Conversion table from ECoS v4.2.9 to PacoMouseCYD function icons // 1, 1, 2, 3, 4, 5, 6, 7, // 8, 9, 10, 11, 12, 13, 14, 15, // 16, 17, 18, 19, 20, 21, 22, 23, // 2, 2, 24, 2, 2, 25, 26, 27, //31 // 28, 29, 30, 2, 31, 2, 32, 2, // 2, 2, 2, 2, 33, 34, 2, 2, //47 // 2, 2, 2, 2, 2, 2, 2, 35, // 2, 36, 2, 2, 2, 2, 2, 2, //63 // 2, 2, 2, 2, 2, 2, 2, 2, // 2, 2, 2, 2, 2, 2, 2, 2, //79 // 2, 2, 2, 2, 37, 2, 2, 38, // 39, 2, 2, 2, 2, 2, 2, 2, //95 // 2, 2, 2, 2, 2, 39, 2, 2, //103 // 2, 2, 2, 2, 2, 2, 2, 2, // 2, 2, 2, 2, 2, 2, 2, 2, // 2, 2, 2, 2, 40, 2, 2, 2, //127 // }; // const uint8_t FunktionsTastenSymboleCS1[] = { // Conversion table from CS1 v2.0.4 to PacoMouseCYD function icons // 2, 10, 3, 14, 5, 31, 15, 16, // 11, 13, 17, 9, 12, 6, 7, 23, // 4, 2, 25, 26, 22, 2, 30, 27, // 2, 2, 2, 28, 29, 20, 21, 8, //31 // 18, 2, 2, 2, 2, 2, 2, 2, // 2, 2, 2, 2, 2, 2, 2, 2, //47 // 2, 2, 2, 2, 2, 2, 2, 2, // 2, 2, 2, 2, 2, 2, 2, 2, //63 // }; //////////////////////////////////////////////////////////// // ***** MAIN PROGRAM ***** //////////////////////////////////////////////////////////// // Arduino setup function: runs once at boot void setup() { uint16_t posLabel; // Variable for label positioning (if used) // initPins(); // Initialize GPIO pins delay(500); // Power-up safety delay // Initialize serial for debug output if enabled #ifdef DEBUG Serial.begin(115200); // Debug messages, serial at 115200b DEBUG_MSG("\n\nPacoMouseCYD v%s.%s", VER_H, VER_L); DEBUG_MSG(USER_SETUP_INFO); DEBUG_MSG("Chip Model: %s \nFlash Chip Size: %lu", ESP.getChipModel(), ESP.getFlashChipSize()) DEBUG_MSG("ESP_ARDUINO_VERSION: %d.%d.%d", ESP_ARDUINO_VERSION_MAJOR, ESP_ARDUINO_VERSION_MINOR, ESP_ARDUINO_VERSION_PATCH) #endif // initGUI(); // Initialize graphical user interface // initVariables(); // Initialize global variables and settings // sdDetected = SD.begin(SD_CS) ? true : false; // Initialize SD card DEBUG_MSG("SD Card %s", sdDetected ? "found" : "begin failed") // tft.init(); // Initialize TFT display // tft.fillScreen(COLOR_BLACK); // Clear display #ifdef DEBUG DEBUG_MSG("Display driver: %X %dx%d", TFT_DRIVER, TFT_WIDTH, TFT_HEIGHT) DEBUG_MSG("TFT BL pin: %d", TFT_BL) DEBUG_MSG("GUI Usage:\n Timers: %d\n Windows: %d\n Labels: %d", MAX_SYS_TIMER, MAX_WIN_OBJ, MAX_LABEL_OBJ) DEBUG_MSG(" Draw Strings: %d\n Chars: %d\n Function Icons: %d", MAX_DRAWSTR_OBJ, MAX_CHAR_OBJ, MAX_FNC_OBJ) DEBUG_MSG(" Icons: %d\n Buttons: %d\n Radio Buttons: %d", MAX_ICON_OBJ, MAX_BUT_OBJ, MAX_RAD_OBJ) DEBUG_MSG(" Progress Bar: %d\n Loco Pictures: %d\n Gauges: %d", MAX_BAR_OBJ, MAX_LPIC_OBJ, MAX_GAUGE_OBJ) DEBUG_MSG(" Text Box: %d\n Switch: %d\n Keyboard: %d", MAX_TXT_OBJ, MAX_SWITCH_OBJ, MAX_KEYB_OBJ) #endif // Setting up the LEDC and configuring the Back light pin // NOTE: this needs to be done after tft.init() #if (ESP_ARDUINO_VERSION_MAJOR > 2) DEBUG_MSG("PWM LED code for version 3.x") ledcAttach(TFT_BL, LEDC_BASE_FREQ, LEDC_RESOLUTION); #else DEBUG_MSG("PWM LED code for version 2.x") // ledcSetup(LEDC_CHANNEL_0, LEDC_BASE_FREQ, LEDC_RESOLUTION); // backlight PWM // ledcAttachPin(TFT_BL, LEDC_CHANNEL_0); #endif // setBacklight (backlight); // Set initial backlight level // // // Setup the touchscreen (must be after tft.init()) // touchscreen.begin(TFT_WIDTH, TFT_HEIGHT); // Initialize touchscreen // setRotationDisplay(locationUSB); // Set display rotation based on USB location // copyOutA = digitalRead (ENCODER_A); // Init encoder ISR // copyOutB = 0x80; // attachInterrupt (digitalPinToInterrupt (ENCODER_A), encoderISR, CHANGE); // Attach encoder interrupt // openWindow(WIN_LOGO); // Show logo window (no events, just draw) // initStatus = initSequence(); // Run initialization sequence // getLastLoco(); // Load last used locomotive } // Arduino main loop: runs repeatedly after setup() void loop() { // timerProcess(); // Handle timers // hidProcess(); // Handle human interface devices (buttons, encoders, etc.) // wifiProcess(); // Handle WiFi events and communication // if (isWindow(WIN_STEAM)) // steamProcess(); // If steam window is active, process steam logic // if (eventsPending > 0) { // Execute pending events // eventProcess(); // DEBUG_MSG("New Event...") // } // // Check BOOT button to enter touchscreen calibration // if (! bootPressed) { // if (digitalRead(SW_BOOT) == LOW) { // bootPressed = true; // newEvent(OBJ_WIN, WIN_CALIBRATE, EVNT_BOOT); // DEBUG_MSG("BOOT switch pressed...") // } // } // // Handle touchscreen click detection // if (clickDetected) { // if (! touchscreen.touched()) // clickDetected = false; // } // else { // if (touchscreen.touched()) { // TSPoint p = touchscreen.getTouch(); // if (p.z > 350) { // DEBUG_MSG("X: %d, Y: %d, Z: %d", p.x, p.y, p.z); // sendClickEvent(p.x, p.y); // clickDetected = true; // } // } // } } //////////////////////////////////////////////////////////// // ***** SUPPORT ***** //////////////////////////////////////////////////////////// void initVariables() { byte pos; uint16_t xm, xM, ym, yM, value; // EEPROM.begin(EEPROM_SIZE); // eepromChanged = false; // currLanguage = EEPROM.read(EE_LANGUAGE); // if (currLanguage >= MAX_LANG) // currLanguage = LANG_ENGLISH; // bootPressed = false; // calibrationPending = false; // EEPROM.get (EE_WIFI, wifiSetting); // read WiFi settings // if (wifiSetting.ok != 0x464D) { // check correct EEPROM signature // snprintf (wifiSetting.ssid, 32, ""); // init WiFi settings // snprintf (wifiSetting.password, 64, "12345678"); // wifiSetting.CS_IP = IPAddress(192, 168, 0, 111); // wifiSetting.port = 1234; // wifiSetting.serverType = true; // wifiSetting.protocol = CLIENT_Z21; // wifiSetting.ok = 0x464D; // EEPROM.put(EE_WIFI, wifiSetting); // also init calibration values // touchscreen.setCalibration(0, 4095, 0, 4095); // set default calibration values // saveCalibrationValues(); // save all // DEBUG_MSG("Setting default WiFi & calibration values"); // } // xm = (EEPROM.read(EE_XMIN_H) << 8) + EEPROM.read(EE_XMIN_L); // xM = (EEPROM.read(EE_XMAX_H) << 8) + EEPROM.read(EE_XMAX_L); // ym = (EEPROM.read(EE_YMIN_H) << 8) + EEPROM.read(EE_YMIN_L); // yM = (EEPROM.read(EE_YMAX_H) << 8) + EEPROM.read(EE_YMAX_L); // touchscreen.setCalibration(xm, xM, ym, yM); // set calibration values // DEBUG_MSG("xMin: %d, xMax: %d, yMin: %d, yMax: %d", xm, xM, ym, yM); // backlight = EEPROM.read(EE_BACKLIGHT); // if (backlight < USER_MIN_BL) // backlight = USER_MIN_BL; // locationUSB = EEPROM.read(EE_USB_LOCATION); // if (locationUSB > 0) // locationUSB = USB_UP; // timeButtons = millis(); // lastTimeEncoder = millis(); // encoderValue = 0; // encoderMax = 2; // encoderNeedService = false; // oldNeedle = 0; // initFIFO(); // accessories // editAccessory = false; // accPanelChanged = false; // currPanel = 0; // myTurnout = 1; // initLastAspects(); // CVdata = 3; // CV programming // CVaddress = 1; // modeProg = false; // enterCVdata = false; // progFinished = false; // progStepCV = PRG_IDLE; // clockRate = 0; // fast clock internal // clockHour = 0; // clockMin = 0; // updateFastClock(); // initLocos(); // stopMode = EEPROM.read(EE_STOP_MODE); // shuntingMode = (EEPROM.read(EE_SHUNTING) > 0) ? true : false; // lockOptions = EEPROM.read(EE_LOCK) & 0x07; // shortAddress = EEPROM.read(EE_SHORT); // if ((shortAddress != 99) && (shortAddress != 127)) // shortAddress = 99; // typeCmdStation = EEPROM.read(EE_CMD_STA); // if (typeCmdStation > CMD_DIG) // typeCmdStation = CMD_DR; // autoIdentifyCS = EEPROM.read(EE_CMD_AUTO); // speedoPhase = SPD_WAIT; // speedoScale = 87; // speedoLength = 1000; // speedoSpeed = 0; // oldSteamLoco = 0; // mySlot.num = 0; // Loconet slots // mySlot.trk = 0x01; // Power on // doDispatchGet = false; // doDispatchPut = false; // lnetProg = false; // ulhiProg = UHLI_PRG_END; // lastTrk = 0x80; // msgDecodePhase = MSG_WAIT; // ECoS // requestedCV = false; // appVer = 4; // staTurnoutAdr1 = staGetTurnoutAdr(EE_STA_ADRH1, 1); // Station Run // staTurnoutAdr2 = staGetTurnoutAdr(EE_STA_ADRH2, 2); // staTurnoutAdr3 = staGetTurnoutAdr(EE_STA_ADRH3, 3); // staTurnoutAdr4 = staGetTurnoutAdr(EE_STA_ADRH4, 4); // staTurnoutDef = EEPROM.read(EE_STA_TRNDEF); // staMaxTurnout = EEPROM.read(EE_STA_TRNNUM); // staMaxTurnout = constrain(staMaxTurnout, 1, 4); // staMaxStations = EEPROM.read(EE_STA_NUM); // staMaxStations = constrain(staMaxStations, 3, 5); // staStartTime = EEPROM.read(EE_STA_TIME); } void hidProcess() { // if (encoderNeedService) // detectado cambio en pines del encoder // encoderService(); // if (encoderChange) // se ha movido el encoder // controlEncoder(); // if (switchOn) // se ha pulsado el boton del encoder // controlSwitch(); // if (millis() - timeButtons > timeoutButtons) // lectura de boton // readButtons(); } void updateFastClock() { // if (clockRate > 0) // sprintf(clockBuf, "%02d:%02d", clockHour, clockMin); // else // sprintf(clockBuf, ""); // if (isWindow(WIN_THROTTLE)) // newEvent(OBJ_TXT, TXT_CLOCK, EVNT_DRAW); } void setSpeedoPhase(uint8_t phase) { // drawStrData[DSTR_SPEEDO_BLANK].x = 40 + (speedoPhase * 32); // speedoPhase = phase; // iconData[ICON_SPEEDO_LOK].x = 40 + (speedoPhase * 32); // drawObject(OBJ_DRAWSTR, DSTR_SPEEDO_BLANK); // drawObject(OBJ_ICON, ICON_SPEEDO_LOK); } //////////////////////////////////////////////////////////// // ***** SOPORTE LOCOMOTORAS ***** //////////////////////////////////////////////////////////// // uint16_t checkLocoAddress(uint16_t loco) { // if (useID) { // } // else { // loco &= 0x3FFF; // if ((loco > 9999) || (loco == 0)) // Comprueba que este entre 1 y 9999 // loco = 3; // } // //DEBUG_MSG("Loco: %d", loco); // return loco; // } // void pushLoco(unsigned int loco) { // mete locomotora en el stack // byte pos; // unsigned int adr; // for (pos = 0; pos < LOCOS_IN_STACK; pos++) { // busca loco en stack // if (locoStack[pos] == loco) // locoStack[pos] = 0; // evita que se repita // } // pos = 0; // do { // adr = locoStack[pos]; // push the loco in stack // locoStack[pos] = loco; // loco = adr; // pos++; // } while ((adr > 0) && (pos < LOCOS_IN_STACK)); // #ifdef DEBUG // Serial.print(F("STACK: ")); // for (pos = 0; pos < LOCOS_IN_STACK; pos++) { // Serial.print(locoStack[pos]); // Serial.print(' '); // } // Serial.println(); // #endif // } // void popLoco(unsigned int loco) { // elimina locomotora del stack // byte pos, n; // unsigned int adr; // pos = LOCOS_IN_STACK; // for (n = 0; n < LOCOS_IN_STACK; n++) { // busca loco en stack // if (locoStack[n] == loco) // pos = n; // save position // } // if (pos != LOCOS_IN_STACK) { // for (n = pos; n < (LOCOS_IN_STACK - 1); n++) { // locoStack[n] = locoStack[n + 1]; // } // locoStack[LOCOS_IN_STACK - 1] = 0; // } // } // void initLocos() { // uint16_t pos; // myLocoData = 0; // for (pos = 0; pos < LOCOS_IN_STACK; pos++) { // locoStack[pos] = 0; // clear loco stack // clearLocoData(pos); // } // } // void clearLocoData (uint16_t pos) { // uint16_t cnt; // locoData[pos].myAddr.address = 0; // clear loco data // locoData[pos].myName[0] = '\0'; // locoData[pos].myFunc.Bits = 0; // locoData[pos].myDir = 0x80; // locoData[pos].mySpeed = 0; // locoData[pos].mySteps = DEFAULT_STEPS; // locoData[pos].myVmax = 100; // locoData[pos].myLocoID = SYS_NO_LOK; // locoData[pos].myFuncIcon[0] = FNC_LIGHT_OFF; // for (cnt = 1; cnt < 29; cnt++) // locoData[pos].myFuncIcon[cnt] = FNC_FUNC_OFF; // locoData[pos].myFuncIcon[cnt] = FNC_BLANK_OFF; // } // void getLastLoco() { // uint16_t loco; // loco = locoStack[0]; // get most recent loco in stack (filesystem) // loco = checkLocoAddress(loco); // avoid empty stack // findLocoData(loco); // get pos in stack also check stack full // loadThrottleData(); // load data to throttle // updateSpeedHID(); // set encoder // setTimer(TMR_INFO, 10, TMR_ONESHOT); // } // void getNewLoco(uint16_t loco) { // if (useID) { // if (loco != locoData[myLocoData].myLocoID) { // releaseLoco(); // } // } // else { // if (loco != locoData[myLocoData].myAddr.address) { // releaseLoco(); // } // } // loco = checkLocoAddress(loco); // findLocoData(loco); // get pos in stack also check stack full // loadThrottleData(); // load data to throttle // updateSpeedHID(); // set encoder // infoLocomotora(loco); // ask current values // setTimer(TMR_INFO, 5, TMR_ONESHOT); // } // void findLocoData(uint16_t loco) { // uint16_t pos, cnt; // pos = 0; // if (useID) { // while (pos < LOCOS_IN_STACK) { // find ID loco data // if (locoData[pos].myLocoID == loco) { // myLocoData = pos; // pushLoco(loco); // DEBUG_MSG("Find ID%d data in %d", loco, pos); // return; // } // pos++; // } // } // else { // while (pos < LOCOS_IN_STACK) { // find address loco data // if (locoData[pos].myAddr.address == loco) { // myLocoData = pos; // pushLoco(loco); // DEBUG_MSG("Find %d data in %d", loco, pos); // return; // } // pos++; // } // DEBUG_MSG("New Loco") // pos = 0; // new loco // while (pos < LOCOS_IN_STACK) { // if (locoData[pos].myAddr.address == 0) { // myLocoData = pos; // locoData[pos].myAddr.address = loco; // locoData[pos].myLocoID = SYS_ELOK; // //locoData[pos].mySteps = DEFAULT_STEPS; // pushLoco(loco); // return; // } // pos++; // } // } // myLocoData = 0; // stack full // alertWindow(ERR_FULL); // } // void loadThrottleData() { // uint16_t n; // snprintf (locoName, NAME_LNG + 1, "%s", locoData[myLocoData].myName ); // txtData[TXT_LOCO_NAME].font = (strlen(locoName) > 13) ? FSS7 : FSS9; // sprintf (locoAddr, "%d", locoData[myLocoData].myAddr.address); // lpicData[LPIC_MAIN].id = locoData[myLocoData].myLocoID; // iconData[ICON_FWD].color = (locoData[myLocoData].myDir & 0x80) ? COLOR_NAVY : COLOR_DARKGREY; // iconData[ICON_REV].color = (locoData[myLocoData].myDir & 0x80) ? COLOR_DARKGREY : COLOR_NAVY; // for (n = 0; n < 10; n++) { // fncData[FNC_FX0 + n].num = n; // fncData[FNC_FX0 + n].idIcon = locoData[myLocoData].myFuncIcon[n]; // } // updateFuncState(false); // } // void updateFuncState(bool show) { // byte n; // bool state; // for (n = 0; n < 10; n++) { // state = bitRead(locoData[myLocoData].myFunc.Bits, fncData[FNC_FX0 + n].num); // if (fncData[FNC_FX0 + n].state != state) { // fncData[FNC_FX0 + n].state = state; // if (show) // newEvent(OBJ_FNC, FNC_FX0 + n, EVNT_DRAW); // DEBUG_MSG("Change in F%d", fncData[FNC_FX0 + n].num); // } // } // } // void toggleFunction(uint8_t fnc, uint8_t id) { // locoData[myLocoData].myFunc.Bits ^= bit(fnc); // fncData[id].state = bitRead(locoData[myLocoData].myFunc.Bits, fnc); // funcOperations(fnc); // newEvent(OBJ_FNC, id, EVNT_DRAW); // } // void showFuncBlock(uint8_t fncOffset) { // uint16_t ini, cnt; // for (ini = 0; ini < 10; ini++) { // cnt = ini + FNC_FX0; // fncData[cnt].idIcon = locoData[myLocoData].myFuncIcon[ini + fncOffset]; // fncData[cnt].num = ini + fncOffset; // fncData[cnt].state = bitRead(locoData[myLocoData].myFunc.Bits, fncData[cnt].num); // drawObject(OBJ_FNC, cnt); // } // } // void showNextFuncBlock() { // uint16_t ini, fncOffset; // ini = fncData[FNC_FX0].num; // fncOffset = (ini == 20) ? 0 : ini + 10; // showFuncBlock(fncOffset); // } // void updateSpeedHID() { // byte spd, steps; // switch (wifiSetting.protocol) { // case CLIENT_Z21: // case CLIENT_XNET: // if (bitRead(locoData[myLocoData].mySteps, 2)) { // 0..127 -> 0..63 // encoderMax = 63; // encoderValue = (locoData[myLocoData].mySpeed > 1) ? (locoData[myLocoData].mySpeed >> 1) : 0; // } // else { // if (bitRead(locoData[myLocoData].mySteps, 1)) { // 0..31 // encoderMax = 31; // spd = (locoData[myLocoData].mySpeed & 0x0F) << 1; // if (bitRead(locoData[myLocoData].mySpeed, 4)) // bitSet(spd, 0);; // encoderValue = (spd > 3) ? spd : 0; // } // else { // 0..15 // encoderMax = 15; // spd = locoData[myLocoData].mySpeed & 0x0F; // encoderValue = (spd > 1) ? spd : 0; // } // } // break; // case CLIENT_LNET: // steps = getMaxStepLnet(); // encoderMax = (steps == 128) ? 63 : ((steps == 28) ? 31 : 15); // if (locoData[myLocoData].mySpeed > 1) { // if (steps == 128) { // Max 100% speed (64 pasos, compatible con 14, 28 y 128 pasos) // encoderValue = locoData[myLocoData].mySpeed >> 1; // 0..127 -> 0..63 // } // else { // if (steps == 28) { // 0..31 // spd = (((locoData[myLocoData].mySpeed - 2) << 1) / 9); // encoderValue = spd + 4; // } // else { // spd = (locoData[myLocoData].mySpeed - 2) / 9; // encoderValue = spd + 2; // } // } // } // else // encoderValue = 0; // //DEBUG_MSG("HID Enc:%d Spd:%d", encoderValue, mySpeed); // break; // case CLIENT_ECOS: // encoderMax = 63; // 0..127 -> 0..63 // encoderValue = (locoData[myLocoData].mySpeed > 1) ? (locoData[myLocoData].mySpeed >> 1) : 0; // break; // } // updateSpeedDir(); // } // void updateMySpeed() { // byte spd, steps; // switch (wifiSetting.protocol) { // case CLIENT_Z21: // case CLIENT_XNET: // if (bitRead(locoData[myLocoData].mySteps, 2)) { // 0..63 -> 0..127 // if ((encoderValue == 0) && shuntingMode) // comprueba Modo maniobras // encoderValue = 1; // locoData[myLocoData].mySpeed = encoderValue << 1; // } // else { // if (bitRead(locoData[myLocoData].mySteps, 1)) { // 0..31 -> 0..31 '---43210' -> '---04321' // encoderValue = shuntingSpeed (encoderValue, 4); // if (encoderValue > 3) { // locoData[myLocoData].mySpeed = (encoderValue >> 1) & 0x0F; // bitWrite(locoData[myLocoData].mySpeed, 4, bitRead(encoderValue, 0)); // } // else { // switch (encoderValue) { // case 0: // case 3: // locoData[myLocoData].mySpeed = 0; // encoderValue = 0; // break; // case 1: // case 2: // locoData[myLocoData].mySpeed = 2; // encoderValue = 4; // break; // } // } // } // else { // encoderValue = shuntingSpeed (encoderValue, 2); // locoData[myLocoData].mySpeed = (encoderValue > 1) ? encoderValue : 0; // 0..15 -> 0..15 // } // } // break; // case CLIENT_LNET: // steps = getMaxStepLnet(); // if (steps == 128) { // if ((encoderValue == 0) && shuntingMode) // Modo maniobras // encoderValue = 1; // locoData[myLocoData].mySpeed = (encoderValue << 1); // 0..63 -> 0..127 // if (encoderValue > 61) // locoData[myLocoData].mySpeed++; // } // else { // if (steps == 28) { // encoderValue = shuntingSpeed (encoderValue, 4); // if (encoderValue > 3) { // spd = ((encoderValue - 3) * 9) + 1; // 0..31 -> 0..127 // locoData[myLocoData].mySpeed = (spd >> 1) + 1; // } // else { // switch (encoderValue) { // case 0: // case 3: // locoData[myLocoData].mySpeed = 0; // encoderValue = 0; // break; // case 1: // case 2: // locoData[myLocoData].mySpeed = 5; // encoderValue = 4; // break; // } // } // } // else { // encoderValue = shuntingSpeed (encoderValue, 2); // if (encoderValue == 1) // return; // locoData[myLocoData].mySpeed = (encoderValue > 1) ? ((encoderValue - 2) * 9) + 2 : 0; // 0..15 -> 0..127 // } // } // //DEBUG_MSG("LN Enc:%d Spd:%d", encoderValue, mySpeed); // break; // case CLIENT_ECOS: // if ((encoderValue == 0) && shuntingMode) // Modo maniobras // encoderValue = 1; // locoData[myLocoData].mySpeed = (encoderValue << 1); // 0..63 -> 0..127 // if (encoderValue > 61) // locoData[myLocoData].mySpeed++; // break; // } // locoOperationSpeed(); // } // byte shuntingSpeed (byte encoder, byte stepMin) { // comprueba Modo maniobras // if (encoder < stepMin) { // if (shuntingMode) // return stepMin; // } // return encoder; // } // void updateSpeedDir() { // uint16_t angle, spd, stp; // iconData[ICON_FWD].color = (locoData[myLocoData].myDir & 0x80) ? COLOR_NAVY : COLOR_DARKGREY; // iconData[ICON_REV].color = (locoData[myLocoData].myDir & 0x80) ? COLOR_DARKGREY : COLOR_NAVY; // if (isWindow(WIN_THROTTLE)) { // angle = map(encoderValue, 0, encoderMax, 0, 255); // // if ((encoderMax == 15) && (encoderValue < 2)) // // angle = 0; // drawObject(OBJ_ICON, ICON_FWD); // drawObject(OBJ_ICON, ICON_REV); // spd = map(angle, 0, 255, 0, locoData[myLocoData].myVmax); // stp = getCurrentStep(); // drawSpeed(angle, spd, stp); // DEBUG_MSG("Enc: %d-%d Spd: %d Stp: %d", encoderValue, encoderMax, spd, stp) // } // if (isWindow(WIN_SPEEDO)) { // gaugeData[GAUGE_SPEEDO].value = map(encoderValue, 0, encoderMax, 0, 255); // fncData[FNC_SPEEDO_DIR].idIcon = (locoData[myLocoData].myDir & 0x80) ? FNC_NEXT_OFF : FNC_PREV_OFF; // drawObject(OBJ_GAUGE, GAUGE_SPEEDO); // drawObject(OBJ_FNC, FNC_SPEEDO_DIR); // drawSpeedoStep(); // } // if (isWindow(WIN_STA_PLAY)) { // gaugeData[GAUGE_STATION].value = map(encoderValue, 0, encoderMax, 0, 255); // fncData[FNC_STA_DIR].idIcon = (locoData[myLocoData].myDir & 0x80) ? FNC_NEXT_OFF : FNC_PREV_OFF; // drawObject(OBJ_GAUGE, GAUGE_STATION); // drawObject(OBJ_FNC, FNC_STA_DIR); // } // } // void drawSpeed(uint16_t angle, uint16_t spd, uint16_t stp) { // angle = (angle < 30) ? 330 + angle : angle - 30; // convert 0..255 to drawing angles -30..225 // tft.setPivot(gaugeData[GAUGE_SPEED].x, gaugeData[GAUGE_SPEED].y); // sprite.setColorDepth(8); // Create an 8bpp Sprite // sprite.createSprite(36, 16); // 8bpp requires 36 * 16 = 576 bytes // sprite.setPivot(56, 7); // Set pivot relative to top left corner of Sprite // sprite.fillSprite(COLOR_BACKGROUND); // Fill the Sprite with background // sprite.pushRotated(oldNeedle); // Delete needle // sprite.drawBitmap(0, 0, needle, 36, 15, COLOR_RED); // Draw new needle // sprite.drawFastHLine(4, 7, 29, COLOR_PINK); // sprite.pushRotated(angle, COLOR_BACKGROUND); // oldNeedle = angle; // sprite.fillSprite(gaugeData[GAUGE_SPEED].color); // Fill the Sprite with black //gaugeData[GAUGE_SPEED].color // sprite.setFreeFont(FSSB9); // sprite.setTextColor(COLOR_WHITE); // sprite.setTextDatum(MC_DATUM); // sprite.drawNumber(spd, 18, 8); // Draw current speed in km/h // sprite.pushSprite(gaugeData[GAUGE_SPEED].x - 18, gaugeData[GAUGE_SPEED].y - 8, COLOR_TRANSPARENT); // sprite.fillSprite(COLOR_BACKGROUND); // Draw current step // sprite.setFreeFont(FSS7); // sprite.drawNumber(stp, 18, 8); // sprite.pushSprite(gaugeData[GAUGE_SPEED].x - 18, gaugeData[GAUGE_SPEED].y + 44, COLOR_TRANSPARENT); // sprite.deleteSprite(); // } // void drawSpeedoStep() { // uint16_t stp; // stp = getCurrentStep(); // sprite.setColorDepth(16); // Create an 16bpp Sprite // sprite.createSprite(36, 16); // 8bpp requires 36 * 16 = 576 bytes * 2 // sprite.fillSprite(COLOR_BACKGROUND); // Draw current step // sprite.setFreeFont(FSS7); // sprite.setTextColor(COLOR_WHITE); // sprite.setTextDatum(MC_DATUM); // sprite.drawNumber(stp, 18, 8); // sprite.pushSprite(gaugeData[GAUGE_SPEEDO].x - 18, gaugeData[GAUGE_SPEEDO].y + 20, COLOR_TRANSPARENT); // sprite.deleteSprite(); // } // uint16_t countLocoInStack() { // uint16_t pos, total; // total = 0; // pos = 0; // while ((locoStack[pos++] > 0) && (pos < LOCOS_IN_STACK)) // total++; // return total; // } // void prepareLocoList() { // uint16_t pos; // for (pos = 0; pos < 6; pos++) { // delete list // txtData[TXT_SEL_ADDR1 + pos].buf[0] = '\0'; // txtData[TXT_SEL_NAME1 + pos].buf[0] = '\0'; // } // encoderValue = 0; // count locos in stack // encoderMax = countLocoInStack(); // DEBUG_MSG("Locos in list: %d", encoderMax); // if (encoderMax > 0) // encoderMax--; // sortLocoList(SORT_LAST); // populateLocoList(); // } // void populateLocoList() { // uint16_t posName; // uint16_t line, adr, n; // for (n = 0; n < 6; n++) { // if (n < encoderMax + 1) { // line = (encoderValue > 5) ? encoderValue - 5 : 0; // adr = sortedLocoStack[line + n]; // posName = findLocoPos(adr); // //DEBUG_MSG("Enc: %d Line: %d Adr: %d", encoderValue, line, adr); // if (useID) // snprintf(txtData[TXT_SEL_ADDR1 + n].buf, ADDR_LNG + 1, "%d", locoData[posName].myAddr.address); // else // snprintf(txtData[TXT_SEL_ADDR1 + n].buf, ADDR_LNG + 1, "%d", adr); // if (posName != LOCOS_IN_STACK) // snprintf(txtData[TXT_SEL_NAME1 + n].buf, NAME_LNG + 1, locoData[posName].myName); // } // } // line = (encoderValue > 5) ? 5 : encoderValue; // for (n = 0; n < 6; n++) { // txtData[TXT_SEL_ADDR1 + n].backgnd = (n == line) ? COLOR_YELLOW : COLOR_WHITE; // txtData[TXT_SEL_NAME1 + n].backgnd = (n == line) ? COLOR_YELLOW : COLOR_WHITE; // } // } // uint16_t findLocoPos(uint16_t loco) { // uint16_t pos, n; // pos = LOCOS_IN_STACK; // for (n = 0; n < LOCOS_IN_STACK; n++) { // if (useID) { // if (locoData[n].myLocoID == loco) // search ID in loco stack // pos = n; // } // else { // if (locoData[n].myAddr.address == loco) // search address in loco stack // pos = n; // } // } // return pos; // } // void sortLocoList (uint16_t order) { // uint16_t tmp, n, i, j, total, pos; // bool reverse; // #ifdef DEBUG // Serial.print(F("INI.STACK: ")); // for (pos = 0; pos < LOCOS_IN_STACK; pos++) { // Serial.print(sortedLocoStack[pos]); // Serial.print(' '); // } // Serial.println(); // #endif // objStack[posObjStack1].objID = ICON_LAST_UP + order; // currOrder = order; // total = countLocoInStack(); // reverse = false; // //DEBUG_MSG("Order %d Total %d", currOrder, total) // switch (order) { // case SORT_LAST: // for (n = 0; n < LOCOS_IN_STACK; n++) // sortedLocoStack[n] = locoStack[n]; // break; // case SORT_NUM_DWN: // reverse = true; // case SORT_NUM_UP: // for (i = 1; i < total; i++) { // if (useID) { // for (j = i; j > 0 && (idOrder(sortedLocoStack[j - 1], sortedLocoStack[j]) != reverse); j--) { // tmp = sortedLocoStack[j - 1]; // sortedLocoStack[j - 1] = sortedLocoStack[j]; // sortedLocoStack[j] = tmp; // } // } // else { // for (j = i; j > 0 && ((sortedLocoStack[j - 1] > sortedLocoStack[j]) != reverse); j--) { // tmp = sortedLocoStack[j - 1]; // sortedLocoStack[j - 1] = sortedLocoStack[j]; // sortedLocoStack[j] = tmp; // } // } // } // break; // case SORT_NAME_DWN: // reverse = true; // case SORT_NAME_UP: // for (i = 1; i < total; i++) { // for (j = i; j > 0 && (nameOrder(sortedLocoStack[j - 1], sortedLocoStack[j]) != reverse); j--) { // tmp = sortedLocoStack[j - 1]; // sortedLocoStack[j - 1] = sortedLocoStack[j]; // sortedLocoStack[j] = tmp; // } // } // break; // } // #ifdef DEBUG // Serial.print(F("END.STACK: ")); // for (pos = 0; pos < LOCOS_IN_STACK; pos++) { // Serial.print(sortedLocoStack[pos]); // Serial.print(' '); // } // Serial.println(); // #endif // } // bool nameOrder(uint16_t first, uint16_t second) { // uint16_t posFirst, posSecond; // posFirst = findLocoPos(first); // posSecond = findLocoPos(second); // return strcmp(locoData[posFirst].myName, locoData[posSecond].myName) > 0; // } // bool idOrder(uint16_t first, uint16_t second) { // uint16_t posFirst, posSecond; // posFirst = findLocoPos(first); // posSecond = findLocoPos(second); // return (locoData[posFirst].myAddr.address > locoData[posSecond].myAddr.address); // } // //////////////////////////////////////////////////////////// // // ***** SOPORTE GUI ***** // //////////////////////////////////////////////////////////// // void setBitsCV() { // uint16_t n; // for (n = 0; n < 8; n++) { // buttonData[BUT_CV_0 + n].backgnd = bitRead(CVdata, n) ? COLOR_BLUE : COLOR_DARKGREY; // charData[CHAR_CV_0 + n].color = bitRead(CVdata, n) ? COLOR_WHITE : COLOR_BROWN; // } // } // void setFieldsCV() { // txtData[TXT_CV].backgnd = enterCVdata ? COLOR_WHITE : COLOR_YELLOW; // txtData[TXT_CV_VAL].backgnd = enterCVdata ? COLOR_YELLOW : COLOR_WHITE; // keybData[KEYB_CV].idTextbox = enterCVdata ? TXT_CV_VAL : TXT_CV; // snprintf(keybCvBuf, ADDR_LNG + 1, "%d", CVaddress); // if (CVdata > 255) { // keybCvValBuf[0] = '\0'; // CVdata = 0; // txtData[TXT_CV_VAL].backgnd = COLOR_PINK; // } // else // snprintf(keybCvValBuf, IP_LNG + 1, "%d", CVdata); // } // void showFieldsCV() { // uint16_t n; // newEvent(OBJ_TXT, TXT_CV, EVNT_DRAW); // newEvent(OBJ_TXT, TXT_CV_VAL, EVNT_DRAW); // for (n = 0; n < 8; n++) // newEvent(OBJ_BUTTON, BUT_CV_0 + n, EVNT_DRAW); // } // void setFieldsLNCV() { // txtData[TXT_LNCV_ART].backgnd = (optLNCV == LNCV_ART) ? COLOR_YELLOW : COLOR_WHITE; // txtData[TXT_LNCV_MOD].backgnd = (optLNCV == LNCV_MOD) ? COLOR_YELLOW : COLOR_WHITE; // txtData[TXT_LNCV_ADR].backgnd = (optLNCV == LNCV_ADR) ? COLOR_YELLOW : COLOR_WHITE; // txtData[TXT_LNCV_VAL].backgnd = (optLNCV == LNCV_VAL) ? COLOR_YELLOW : COLOR_WHITE; // switch (optLNCV) { // case LNCV_ART: // keybData[KEYB_LNCV].idTextbox = TXT_LNCV_ART; // break; // case LNCV_MOD: // keybData[KEYB_LNCV].idTextbox = TXT_LNCV_MOD; // break; // case LNCV_ADR: // keybData[KEYB_LNCV].idTextbox = TXT_LNCV_ADR; // break; // case LNCV_VAL: // keybData[KEYB_LNCV].idTextbox = TXT_LNCV_VAL; // break; // } // snprintf(keybLncvArtBuf, PORT_LNG + 1, "%d", artNum); // snprintf(keybLncvModBuf, PORT_LNG + 1, "%d", modNum); // snprintf(keybLncvAdrBuf, PORT_LNG + 1, "%d", numLNCV); // snprintf(keybLncvValBuf, PORT_LNG + 1, "%d", valLNCV); // } // void showFieldsLNCV() { // newEvent(OBJ_TXT, TXT_LNCV_ART, EVNT_DRAW); // newEvent(OBJ_TXT, TXT_LNCV_MOD, EVNT_DRAW); // newEvent(OBJ_TXT, TXT_LNCV_ADR, EVNT_DRAW); // newEvent(OBJ_TXT, TXT_LNCV_VAL, EVNT_DRAW); // } // void setStatusCV() { // uint16_t num; // char buf[MAX_LABEL_LNG]; // num = 0; // if (CVdata > 255) { // num = LBL_CV_ERROR; // txtData[TXT_CV_STATUS].color = COLOR_RED; // } // else { // txtData[TXT_CV_STATUS].color = COLOR_BLUE; // switch (CVaddress) { // case 1: // case 17: // case 18: // case 19: // num = LBL_CV_ADDR; // break; // case 2: // num = LBL_CV_SPD_L; // break; // case 3: // num = LBL_CV_ACC; // break; // case 4: // num = LBL_CV_DEC; // break; // case 5: // num = LBL_CV_SPD_H; // break; // case 6: // num = LBL_CV_SPD_M; // break; // case 541: // case 29: // num = LBL_CV_CFG; // break; // case 520: // case 8: // switch (CVdata) { // imprime el fabricante conocido // case 13: // sprintf(buf, "DIY"); // break; // case 74: // sprintf(buf, "PpP"); // break; // case 42: // sprintf(buf, "Digikeijs"); // break; // case 151: // sprintf(buf, "ESU"); // break; // case 145: // sprintf(buf, "Zimo"); // break; // case 99: // sprintf(buf, "Lenz"); // break; // case 97: // sprintf(buf, "D&H"); // break; // case 157: // sprintf(buf, "Kuehn"); // break; // case 62: // sprintf(buf, "Tams"); // break; // case 85: // sprintf(buf, "Uhlenbrock"); // break; // case 134: // sprintf(buf, "Lais"); // break; // case 129: // sprintf(buf, "Digitrax"); // break; // case 161: // sprintf(buf, "Roco"); // break; // case 109: // sprintf(buf, "Viessmann"); // break; // case 78: // sprintf(buf, "Train-O-matic"); // break; // case 117: // sprintf(buf, "CT Elektronik"); // break; // default: // num = LBL_CV_MAN; // break; // } // break; // default: // num = LBL_MENU_CV; // break; // } // } // if (num > 0) // getLabelTxt(num, buf); // snprintf(cvStatusBuf, PWD_LNG + 1, "%s", buf); // } // void setTextSpeedo() { // uint8_t index; // char scaleName[5]; // switch (speedoScale) { // case 87: // index = LBL_SCALE_H0; // break; // case 160: // index = LBL_SCALE_N; // break; // case 120: // index = LBL_SCALE_TT; // break; // case 220: // index = LBL_SCALE_Z; // break; // case 45: // index = LBL_SCALE_0; // break; // default: // index = 0; // } // scaleName[0] = '\0'; // if (index > 0) // getLabelTxt(index, scaleName); // snprintf(spdScaleBuf, NAME_LNG + 1, "%s 1:%d", scaleName, speedoScale); // snprintf(spdSelScaleBuf, NAME_LNG + 1, "%s 1:", scaleName); // snprintf(spdSelScaleNumBuf, ADDR_LNG + 1, "%d", speedoScale); // } // void setScaleSpeedo(uint16_t value) { // speedoScale = value; // setTextSpeedo(); // drawObject(OBJ_TXT, TXT_EDIT_SCALE); // drawObject(OBJ_TXT, TXT_NUM_SCALE); // }