Debut projet Platformio

This commit is contained in:
Serge NOEL
2026-02-11 10:10:08 +01:00
parent ba66561087
commit 52c72d27d8
766 changed files with 495205 additions and 0 deletions

View File

@@ -0,0 +1,164 @@
/* 7pt font https://rop.nl/truetype2gfx/ */
const uint8_t FreeSans7pt7bBitmaps[] = {
0x00, 0xFF, 0x40, 0xB6, 0xD0, 0x12, 0x28, 0xD3, 0xF2, 0x44, 0xBF, 0x94,
0x48, 0x90, 0x10, 0x71, 0x52, 0x95, 0x0E, 0x07, 0x09, 0xD2, 0xA5, 0xF0,
0x81, 0x00, 0x00, 0x87, 0x88, 0x89, 0x08, 0x90, 0x72, 0x00, 0x00, 0x04,
0xE0, 0x91, 0x09, 0x11, 0x0E, 0x30, 0x48, 0x48, 0x48, 0x30, 0x52, 0x9A,
0x8C, 0x8E, 0xFA, 0xF0, 0x29, 0x49, 0x24, 0x99, 0x22, 0x91, 0x24, 0x9A,
0x49, 0x48, 0x4F, 0x4A, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0xD8, 0xE0,
0xC0, 0x10, 0x22, 0x04, 0x44, 0x88, 0x33, 0x68, 0xE1, 0x86, 0x18, 0x61,
0x89, 0xE0, 0x13, 0xF1, 0x11, 0x11, 0x11, 0x18, 0xCD, 0x0A, 0x10, 0x61,
0x8C, 0x30, 0x40, 0xFC, 0x38, 0xC9, 0x18, 0x31, 0xC3, 0x80, 0xE1, 0x46,
0xF8, 0x04, 0x18, 0x71, 0xE2, 0xC9, 0xB3, 0x7F, 0x0C, 0x18, 0x7E, 0x81,
0x02, 0x07, 0xC8, 0xC0, 0x81, 0x46, 0xF8, 0x31, 0x28, 0x60, 0xFB, 0x38,
0x61, 0x8D, 0xE0, 0xFC, 0x10, 0x82, 0x10, 0xC2, 0x08, 0x61, 0x00, 0x18,
0xC9, 0x1A, 0x33, 0xCD, 0x90, 0xA1, 0x46, 0xF8, 0x73, 0x68, 0xE1, 0x8F,
0x72, 0x43, 0x8B, 0xE0, 0xC0, 0x0C, 0x40, 0x0D, 0xC0, 0x00, 0x33, 0x30,
0xE0, 0xE0, 0x40, 0xFC, 0x00, 0x00, 0x03, 0x03, 0x03, 0x1D, 0xC8, 0x00,
0x7B, 0x38, 0x43, 0x18, 0x43, 0x00, 0x00, 0xC0, 0x0F, 0xC0, 0xC3, 0x08,
0x04, 0x8E, 0xB4, 0xCC, 0xA4, 0x47, 0x22, 0x29, 0x12, 0x4E, 0xE3, 0x00,
0x0E, 0x00, 0x1F, 0x00, 0x0C, 0x0E, 0x05, 0x02, 0xC2, 0x21, 0x11, 0xFC,
0x82, 0x41, 0x60, 0xC0, 0xFC, 0x86, 0x82, 0x82, 0xFC, 0x86, 0x82, 0x83,
0x82, 0xFC, 0x3E, 0x43, 0xC1, 0x80, 0x80, 0x80, 0x81, 0x81, 0x43, 0x7E,
0xFC, 0xC6, 0xC3, 0xC1, 0xC1, 0xC1, 0xC1, 0xC3, 0xC6, 0xFC, 0xFF, 0x83,
0x06, 0x0F, 0xF8, 0x30, 0x60, 0xC1, 0xFC, 0xFF, 0x83, 0x06, 0x0F, 0xD8,
0x30, 0x60, 0xC1, 0x80, 0x3E, 0x21, 0xB0, 0x50, 0x08, 0x04, 0x3E, 0x03,
0x03, 0x41, 0x9F, 0x40, 0x81, 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81,
0x81, 0x81, 0xFF, 0xFF, 0xF0, 0x04, 0x10, 0x41, 0x04, 0x10, 0x71, 0xCD,
0xE0, 0x82, 0x84, 0x88, 0x90, 0xB0, 0xD8, 0x8C, 0x84, 0x86, 0x83, 0x82,
0x08, 0x20, 0x82, 0x08, 0x20, 0x83, 0xF0, 0xC1, 0xE0, 0xE8, 0x74, 0x7A,
0x2C, 0x96, 0x5B, 0x29, 0x8C, 0xC6, 0x40, 0xC1, 0xC1, 0xA1, 0xB1, 0x91,
0x89, 0x8D, 0x85, 0x87, 0x83, 0x3E, 0x21, 0xA0, 0x70, 0x18, 0x0C, 0x06,
0x03, 0x03, 0x43, 0x1F, 0x00, 0xFD, 0x8F, 0x0E, 0x1C, 0x7F, 0xB0, 0x60,
0xC1, 0x80, 0x3E, 0x21, 0xA0, 0x70, 0x18, 0x0C, 0x06, 0x03, 0x0B, 0x43,
0x1F, 0x80, 0x20, 0xFE, 0xC3, 0xC1, 0xC1, 0xC6, 0xFE, 0xC3, 0xC3, 0xC3,
0xC1, 0x7C, 0xC6, 0x82, 0xC0, 0x70, 0x1E, 0x02, 0x83, 0xC2, 0x7C, 0xFF,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x81, 0x81, 0x81,
0x81, 0x81, 0x81, 0x81, 0x81, 0xC3, 0x7E, 0x41, 0xA0, 0x98, 0x44, 0x62,
0x21, 0x90, 0x58, 0x28, 0x1C, 0x06, 0x00, 0xC2, 0x1A, 0x38, 0x91, 0x44,
0xCA, 0x26, 0x53, 0x14, 0x50, 0xA2, 0x85, 0x14, 0x38, 0xE0, 0x82, 0x00,
0x41, 0x11, 0x8C, 0x82, 0x80, 0xC0, 0xE0, 0x50, 0x44, 0x63, 0x20, 0xC0,
0xC1, 0xB0, 0x88, 0xC6, 0xC1, 0x40, 0x60, 0x20, 0x10, 0x08, 0x04, 0x00,
0x7F, 0x03, 0x06, 0x04, 0x08, 0x18, 0x30, 0x60, 0x40, 0xFF, 0xEA, 0xAA,
0xAA, 0xC0, 0x88, 0x04, 0x40, 0x22, 0x01, 0xE4, 0x92, 0x49, 0x24, 0x9E,
0x23, 0x15, 0x29, 0x00, 0x00, 0x44, 0xFA, 0x20, 0xBE, 0x8A, 0x2F, 0xC0,
0x82, 0x08, 0x3E, 0x8E, 0x18, 0x61, 0x8F, 0xE0, 0x7C, 0x89, 0x06, 0x0C,
0x08, 0x9F, 0x00, 0x02, 0x04, 0x0B, 0xD4, 0x78, 0x70, 0xE1, 0x46, 0xF4,
0x7A, 0x38, 0x7F, 0x82, 0x37, 0x80, 0x34, 0x4F, 0x44, 0x44, 0x44, 0x7A,
0x8F, 0x1E, 0x1C, 0x68, 0xDE, 0x83, 0x44, 0x70, 0x82, 0x08, 0x3E, 0x8E,
0x18, 0x61, 0x86, 0x10, 0x9F, 0xC0, 0x41, 0x55, 0x55, 0xC0, 0x82, 0x08,
0x26, 0xB3, 0x8F, 0x24, 0x8A, 0x30, 0xFF, 0xC0, 0xFF, 0xC6, 0x62, 0x31,
0x18, 0x8C, 0x46, 0x22, 0xFA, 0x38, 0x61, 0x86, 0x18, 0x40, 0x7C, 0x8D,
0x0E, 0x14, 0x28, 0xCF, 0x00, 0xFA, 0x38, 0x61, 0x86, 0x3F, 0xA0, 0x82,
0x00, 0x7E, 0x8F, 0x0E, 0x1C, 0x28, 0xDE, 0x81, 0x02, 0x04, 0xFA, 0x49,
0x20, 0x7D, 0x14, 0x0E, 0x05, 0x17, 0xC0, 0x4B, 0xA4, 0x92, 0x60, 0x8E,
0x38, 0xE3, 0x8E, 0x3F, 0x40, 0xC4, 0x89, 0x33, 0x42, 0x86, 0x0C, 0x00,
0xCC, 0xD3, 0x24, 0xC9, 0x5E, 0x73, 0x0C, 0xC3, 0x30, 0x44, 0xA3, 0x84,
0x29, 0xA4, 0x40, 0x44, 0x89, 0x31, 0x42, 0x86, 0x04, 0x08, 0x20, 0xC0,
0x7C, 0x31, 0x8C, 0x21, 0x0F, 0xC0, 0x69, 0x25, 0xA6, 0x49, 0x26, 0xFF,
0xF8, 0xC6, 0x66, 0x22, 0x12, 0x26, 0x66, 0xC0, 0xE6, 0x70 };
const GFXglyph FreeSans7pt7bGlyphs[] PROGMEM = {
{ 0, 1, 1, 4, 0, 0 }, // 0x20 ' '
{ 1, 1, 10, 4, 2, -9 }, // 0x21 '!'
{ 3, 3, 4, 5, 1, -9 }, // 0x22 '"'
{ 5, 7, 10, 8, 0, -9 }, // 0x23 '#'
{ 14, 7, 13, 8, 0, -10 }, // 0x24 '$'
{ 26, 12, 10, 12, 0, -9 }, // 0x25 '%'
{ 41, 8, 10, 9, 1, -9 }, // 0x26 '&'
{ 51, 1, 4, 3, 1, -9 }, // 0x27 '''
{ 52, 3, 13, 5, 1, -9 }, // 0x28 '('
{ 57, 3, 13, 5, 1, -9 }, // 0x29 ')'
{ 62, 4, 4, 5, 1, -9 }, // 0x2A '*'
{ 64, 6, 7, 8, 1, -6 }, // 0x2B '+'
{ 70, 2, 3, 4, 1, 0 }, // 0x2C ','
{ 71, 3, 1, 5, 1, -3 }, // 0x2D '-'
{ 72, 2, 1, 4, 1, 0 }, // 0x2E '.'
{ 73, 4, 10, 4, 0, -9 }, // 0x2F '/'
{ 78, 6, 10, 8, 1, -9 }, // 0x30 '0'
{ 86, 4, 10, 8, 1, -9 }, // 0x31 '1'
{ 91, 7, 10, 8, 0, -9 }, // 0x32 '2'
{ 100, 7, 10, 8, 0, -9 }, // 0x33 '3'
{ 109, 7, 10, 8, 0, -9 }, // 0x34 '4'
{ 118, 7, 10, 8, 0, -9 }, // 0x35 '5'
{ 127, 6, 10, 8, 1, -9 }, // 0x36 '6'
{ 135, 6, 10, 8, 1, -9 }, // 0x37 '7'
{ 143, 7, 10, 8, 0, -9 }, // 0x38 '8'
{ 152, 6, 10, 8, 1, -9 }, // 0x39 '9'
{ 160, 2, 7, 4, 1, -6 }, // 0x3A ':'
{ 162, 2, 9, 4, 1, -6 }, // 0x3B ';'
{ 165, 6, 7, 8, 1, -6 }, // 0x3C '<'
{ 171, 6, 3, 8, 1, -4 }, // 0x3D '='
{ 174, 6, 7, 8, 1, -6 }, // 0x3E '>'
{ 180, 6, 10, 8, 1, -9 }, // 0x3F '?'
{ 188, 13, 12, 14, 0, -9 }, // 0x40 '@'
{ 208, 9, 10, 9, 0, -9 }, // 0x41 'A'
{ 220, 8, 10, 9, 1, -9 }, // 0x42 'B'
{ 230, 8, 10, 10, 1, -9 }, // 0x43 'C'
{ 240, 8, 10, 10, 1, -9 }, // 0x44 'D'
{ 250, 7, 10, 9, 1, -9 }, // 0x45 'E'
{ 259, 7, 10, 8, 1, -9 }, // 0x46 'F'
{ 268, 9, 10, 11, 1, -9 }, // 0x47 'G'
{ 280, 8, 10, 10, 1, -9 }, // 0x48 'H'
{ 290, 2, 10, 4, 1, -9 }, // 0x49 'I'
{ 293, 6, 10, 7, 0, -9 }, // 0x4A 'J'
{ 301, 8, 10, 9, 1, -9 }, // 0x4B 'K'
{ 311, 6, 10, 8, 1, -9 }, // 0x4C 'L'
{ 319, 9, 10, 11, 1, -9 }, // 0x4D 'M'
{ 331, 8, 10, 10, 1, -9 }, // 0x4E 'N'
{ 341, 9, 10, 11, 1, -9 }, // 0x4F 'O'
{ 353, 7, 10, 9, 1, -9 }, // 0x50 'P'
{ 362, 9, 11, 11, 1, -9 }, // 0x51 'Q'
{ 375, 8, 10, 10, 1, -9 }, // 0x52 'R'
{ 385, 8, 10, 9, 1, -9 }, // 0x53 'S'
{ 395, 8, 10, 8, 0, -9 }, // 0x54 'T'
{ 405, 8, 10, 10, 1, -9 }, // 0x55 'U'
{ 415, 9, 10, 9, 0, -9 }, // 0x56 'V'
{ 427, 13, 10, 13, 0, -9 }, // 0x57 'W'
{ 444, 9, 10, 9, 0, -9 }, // 0x58 'X'
{ 456, 9, 10, 9, 0, -9 }, // 0x59 'Y'
{ 468, 8, 10, 8, 0, -9 }, // 0x5A 'Z'
{ 478, 2, 13, 4, 1, -9 }, // 0x5B '['
{ 482, 4, 10, 4, 0, -9 }, // 0x5C '\'
{ 487, 3, 13, 4, 0, -9 }, // 0x5D ']'
{ 492, 5, 5, 6, 1, -9 }, // 0x5E '^'
{ 496, 8, 1, 8, 0, 3 }, // 0x5F '_'
{ 497, 3, 2, 5, 0, -9 }, // 0x60 '`'
{ 498, 6, 7, 8, 1, -6 }, // 0x61 'a'
{ 504, 6, 10, 8, 1, -9 }, // 0x62 'b'
{ 512, 7, 7, 7, 0, -6 }, // 0x63 'c'
{ 519, 7, 10, 8, 0, -9 }, // 0x64 'd'
{ 528, 6, 7, 8, 1, -6 }, // 0x65 'e'
{ 534, 4, 10, 4, 0, -9 }, // 0x66 'f'
{ 539, 7, 10, 8, 0, -6 }, // 0x67 'g'
{ 548, 6, 10, 8, 1, -9 }, // 0x68 'h'
{ 556, 1, 10, 3, 1, -9 }, // 0x69 'i'
{ 558, 2, 13, 3, 0, -9 }, // 0x6A 'j'
{ 562, 6, 10, 7, 1, -9 }, // 0x6B 'k'
{ 570, 1, 10, 3, 1, -9 }, // 0x6C 'l'
{ 572, 9, 7, 11, 1, -6 }, // 0x6D 'm'
{ 580, 6, 7, 8, 1, -6 }, // 0x6E 'n'
{ 586, 7, 7, 8, 0, -6 }, // 0x6F 'o'
{ 593, 6, 10, 8, 1, -6 }, // 0x70 'p'
{ 601, 7, 10, 8, 0, -6 }, // 0x71 'q'
{ 610, 3, 7, 5, 1, -6 }, // 0x72 'r'
{ 613, 6, 7, 7, 0, -6 }, // 0x73 's'
{ 619, 3, 9, 4, 0, -8 }, // 0x74 't'
{ 623, 6, 7, 8, 1, -6 }, // 0x75 'u'
{ 629, 7, 7, 7, 0, -6 }, // 0x76 'v'
{ 636, 10, 7, 10, 0, -6 }, // 0x77 'w'
{ 645, 6, 7, 7, 0, -6 }, // 0x78 'x'
{ 651, 7, 10, 7, 0, -6 }, // 0x79 'y'
{ 660, 6, 7, 7, 0, -6 }, // 0x7A 'z'
{ 666, 3, 13, 5, 1, -9 }, // 0x7B '{'
{ 671, 1, 13, 4, 1, -9 }, // 0x7C '|'
{ 673, 4, 13, 5, 0, -9 }, // 0x7D '}'
{ 680, 6, 2, 8, 1, -5 } }; // 0x7E '~'
const GFXfont FreeSans7pt7b = {
(uint8_t *)FreeSans7pt7bBitmaps,
(GFXglyph *)FreeSans7pt7bGlyphs,
0x20, 0x7E, 23 };
// Approx. 1354 bytes

View File

@@ -0,0 +1,153 @@
/* 6pt font https://rop.nl/truetype2gfx/ */
const uint8_t FreeSansBold6pt7bBitmaps[] = {
0x00, 0xFF, 0x57, 0xC0, 0xBB, 0x90, 0x2C, 0x5B, 0xFB, 0x44, 0x9F, 0x96,
0x28, 0x11, 0xFD, 0xF4, 0x70, 0x71, 0x77, 0x7C, 0x40, 0x71, 0x36, 0x8D,
0x81, 0xD0, 0x0B, 0xC2, 0x91, 0x24, 0x4F, 0x00, 0xF1, 0x63, 0x87, 0x1E,
0xA7, 0x66, 0xFE, 0xE0, 0x32, 0x64, 0x4C, 0xC4, 0x46, 0x20, 0x89, 0x92,
0x49, 0x69, 0x00, 0x2A, 0x65, 0x21, 0x09, 0xF2, 0x10, 0xF2, 0xFF, 0xF0,
0x24, 0x24, 0x84, 0x01, 0xE4, 0xF3, 0xCF, 0x3C, 0xD3, 0x78, 0x3C, 0x92,
0x49, 0x01, 0xF4, 0xF3, 0x0C, 0x66, 0x18, 0xFC, 0x01, 0xEC, 0xC3, 0x18,
0x30, 0xF3, 0x78, 0x18, 0xA2, 0x92, 0xCB, 0xF0, 0x82, 0x7D, 0x05, 0x1F,
0x0C, 0x3C, 0xDE, 0x01, 0xF4, 0xF0, 0xFF, 0x3C, 0x53, 0x78, 0xFC, 0x31,
0x84, 0x30, 0xC2, 0x08, 0x01, 0xE4, 0xD3, 0x39, 0x3C, 0x73, 0x78, 0x01,
0xEC, 0xF3, 0xCD, 0xF0, 0xD3, 0x78, 0xF0, 0xF0, 0xF0, 0xF7, 0x00, 0x77,
0x30, 0x38, 0x10, 0xFF, 0xC1, 0xF0, 0x83, 0x81, 0xC3, 0x7B, 0x00, 0x73,
0xE8, 0x82, 0x18, 0xC2, 0x0C, 0x30, 0x0F, 0x06, 0x18, 0x81, 0x27, 0x94,
0x93, 0x22, 0x64, 0x4A, 0xFE, 0x60, 0x06, 0x20, 0x38, 0x00, 0x18, 0x1C,
0x1C, 0x34, 0x36, 0x26, 0x7E, 0x63, 0xC3, 0xF9, 0xFB, 0x1E, 0x2F, 0x98,
0xF1, 0xE7, 0xFC, 0x38, 0xFB, 0x1C, 0x08, 0x10, 0x31, 0xE3, 0x7C, 0xF1,
0xFB, 0x1E, 0x3C, 0x78, 0xF1, 0xE6, 0xF8, 0xFF, 0xFC, 0x30, 0xFF, 0x0C,
0x30, 0xFC, 0xFF, 0xFC, 0x30, 0xFB, 0x0C, 0x30, 0xC0, 0x1C, 0x3F, 0x63,
0x40, 0x47, 0x47, 0x61, 0x73, 0x3F, 0xC7, 0x8F, 0x1E, 0x3F, 0xF8, 0xF1,
0xE3, 0xC6, 0xFF, 0xFF, 0xC0, 0x0C, 0x30, 0xC3, 0x0C, 0x3C, 0xF3, 0x78,
0xC7, 0x9B, 0x67, 0x8F, 0x1B, 0x36, 0x66, 0xC6, 0xC3, 0x0C, 0x30, 0xC3,
0x0C, 0x30, 0xFC, 0xC7, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, 0xFB, 0xDB, 0xDB,
0xC7, 0x8F, 0x9F, 0x3F, 0x7B, 0xF3, 0xE7, 0xC6, 0x1C, 0x1F, 0x98, 0xC8,
0x3C, 0x1A, 0x0D, 0x84, 0xE6, 0x3E, 0x00, 0xF3, 0xFC, 0x71, 0xFF, 0xEC,
0x30, 0xC0, 0x1C, 0x1F, 0x98, 0xC8, 0x34, 0x1A, 0x0D, 0x94, 0xE6, 0x3F,
0x80, 0x00, 0xF9, 0xFF, 0x1E, 0x3F, 0xDF, 0xB1, 0xE3, 0xC6, 0x38, 0xFD,
0x1B, 0x07, 0xC1, 0xF0, 0xB3, 0x7E, 0xFF, 0xFC, 0x60, 0xC1, 0x83, 0x06,
0x0C, 0x18, 0xC7, 0x8F, 0x1E, 0x3C, 0x78, 0xF1, 0xE6, 0x7C, 0xC3, 0x42,
0x66, 0x66, 0x24, 0x3C, 0x3C, 0x18, 0x18, 0xC4, 0x79, 0xCD, 0x39, 0x35,
0x66, 0xAC, 0x57, 0x8E, 0xE1, 0x8C, 0x31, 0x80, 0xC7, 0x66, 0x3C, 0x3C,
0x18, 0x38, 0x3C, 0x66, 0x66, 0xC3, 0x66, 0x66, 0x3C, 0x3C, 0x18, 0x18,
0x18, 0x18, 0xFF, 0xFC, 0x30, 0xE1, 0x86, 0x18, 0x60, 0xFE, 0xFE, 0x49,
0x24, 0x93, 0x80, 0x91, 0x24, 0x49, 0xFC, 0x92, 0x49, 0x27, 0x80, 0x63,
0x95, 0xB8, 0x80, 0xFE, 0x48, 0x7F, 0x33, 0xF3, 0xCD, 0xF0, 0x82, 0x08,
0x3E, 0xCE, 0x38, 0xF2, 0xF8, 0x7D, 0x3C, 0x30, 0x4D, 0xE0, 0x04, 0x10,
0x5F, 0x4F, 0x1C, 0x53, 0x7C, 0x7B, 0x3F, 0xF0, 0x4D, 0xE0, 0x37, 0x6F,
0x66, 0x66, 0x60, 0x7D, 0x3C, 0x71, 0x6D, 0xF0, 0x5F, 0x10, 0x84, 0x21,
0xFC, 0xC6, 0x31, 0x88, 0xDF, 0x80, 0x51, 0x55, 0x5F, 0x84, 0x21, 0x3B,
0x73, 0xD3, 0x98, 0xFF, 0x80, 0xFF, 0x6C, 0xE6, 0x73, 0x39, 0x9C, 0xCC,
0xFE, 0x63, 0x18, 0xC4, 0x7C, 0x8B, 0x1E, 0x36, 0x4F, 0x80, 0xFB, 0x38,
0xE3, 0xCB, 0xE8, 0x20, 0x80, 0x7D, 0x3C, 0x71, 0x4D, 0xF0, 0x41, 0x04,
0xBA, 0x49, 0x00, 0x7D, 0x37, 0x87, 0xCD, 0xF0, 0x66, 0xF6, 0x66, 0x67,
0x8C, 0x63, 0x1D, 0xFC, 0xCD, 0x36, 0x8A, 0x38, 0xC0, 0xCD, 0xAE, 0x95,
0x4E, 0xE7, 0x71, 0xB0, 0x6D, 0xE3, 0x0E, 0x69, 0x30, 0xCD, 0x36, 0x9A,
0x38, 0xC3, 0x1C, 0x40, 0xF8, 0x63, 0x18, 0xC3, 0xF0, 0x37, 0x66, 0x6C,
0x66, 0x66, 0x30, 0xFF, 0xE0, 0x99, 0x24, 0xDA, 0x4B, 0x00, 0xC5, 0xC0 };
const GFXglyph FreeSansBold6pt7bGlyphs[] PROGMEM = {
{ 0, 1, 1, 3, 0, 0 }, // 0x20 ' '
{ 1, 2, 9, 4, 1, -8 }, // 0x21 '!'
{ 4, 4, 3, 6, 1, -8 }, // 0x22 '"'
{ 6, 7, 8, 7, 0, -7 }, // 0x23 '#'
{ 13, 6, 10, 7, 0, -8 }, // 0x24 '$'
{ 21, 10, 8, 10, 0, -7 }, // 0x25 '%'
{ 31, 7, 9, 8, 1, -8 }, // 0x26 '&'
{ 39, 1, 3, 3, 1, -8 }, // 0x27 '''
{ 40, 4, 11, 4, 0, -8 }, // 0x28 '('
{ 46, 3, 11, 4, 0, -8 }, // 0x29 ')'
{ 51, 4, 4, 5, 0, -8 }, // 0x2A '*'
{ 53, 5, 6, 7, 1, -5 }, // 0x2B '+'
{ 57, 2, 4, 3, 1, -1 }, // 0x2C ','
{ 58, 4, 2, 4, 0, -3 }, // 0x2D '-'
{ 59, 2, 2, 3, 1, -1 }, // 0x2E '.'
{ 60, 3, 8, 3, 0, -7 }, // 0x2F '/'
{ 63, 6, 9, 7, 0, -8 }, // 0x30 '0'
{ 70, 3, 8, 7, 1, -7 }, // 0x31 '1'
{ 73, 6, 9, 7, 0, -8 }, // 0x32 '2'
{ 80, 6, 9, 7, 0, -8 }, // 0x33 '3'
{ 87, 6, 8, 7, 0, -7 }, // 0x34 '4'
{ 93, 6, 8, 7, 0, -7 }, // 0x35 '5'
{ 99, 6, 9, 7, 0, -8 }, // 0x36 '6'
{ 106, 6, 8, 7, 0, -7 }, // 0x37 '7'
{ 112, 6, 9, 7, 0, -8 }, // 0x38 '8'
{ 119, 6, 9, 7, 0, -8 }, // 0x39 '9'
{ 126, 2, 6, 4, 1, -5 }, // 0x3A ':'
{ 128, 2, 8, 4, 1, -5 }, // 0x3B ';'
{ 130, 6, 6, 7, 0, -5 }, // 0x3C '<'
{ 135, 5, 4, 7, 1, -4 }, // 0x3D '='
{ 138, 6, 6, 7, 0, -5 }, // 0x3E '>'
{ 143, 6, 9, 7, 1, -8 }, // 0x3F '?'
{ 150, 11, 11, 11, 0, -8 }, // 0x40 '@'
{ 166, 8, 9, 8, 0, -8 }, // 0x41 'A'
{ 175, 7, 9, 8, 1, -8 }, // 0x42 'B'
{ 183, 7, 9, 8, 1, -8 }, // 0x43 'C'
{ 191, 7, 9, 8, 1, -8 }, // 0x44 'D'
{ 199, 6, 9, 8, 1, -8 }, // 0x45 'E'
{ 206, 6, 9, 7, 1, -8 }, // 0x46 'F'
{ 213, 8, 9, 9, 0, -8 }, // 0x47 'G'
{ 222, 7, 9, 8, 1, -8 }, // 0x48 'H'
{ 230, 2, 9, 3, 1, -8 }, // 0x49 'I'
{ 233, 6, 9, 7, 0, -8 }, // 0x4A 'J'
{ 240, 7, 9, 8, 1, -8 }, // 0x4B 'K'
{ 248, 6, 9, 7, 1, -8 }, // 0x4C 'L'
{ 255, 8, 9, 10, 1, -8 }, // 0x4D 'M'
{ 264, 7, 9, 8, 1, -8 }, // 0x4E 'N'
{ 272, 9, 9, 9, 0, -8 }, // 0x4F 'O'
{ 283, 6, 9, 8, 1, -8 }, // 0x50 'P'
{ 290, 9, 10, 9, 0, -8 }, // 0x51 'Q'
{ 302, 7, 9, 8, 1, -8 }, // 0x52 'R'
{ 310, 7, 9, 8, 0, -8 }, // 0x53 'S'
{ 318, 7, 9, 7, 0, -8 }, // 0x54 'T'
{ 326, 7, 9, 8, 1, -8 }, // 0x55 'U'
{ 334, 8, 9, 8, 0, -8 }, // 0x56 'V'
{ 343, 11, 9, 11, 0, -8 }, // 0x57 'W'
{ 356, 8, 9, 8, 0, -8 }, // 0x58 'X'
{ 365, 8, 9, 8, 0, -8 }, // 0x59 'Y'
{ 374, 7, 9, 7, 0, -8 }, // 0x5A 'Z'
{ 382, 3, 11, 4, 1, -8 }, // 0x5B '['
{ 387, 3, 8, 3, 0, -7 }, // 0x5C '\'
{ 390, 3, 11, 4, 0, -8 }, // 0x5D ']'
{ 395, 5, 5, 7, 1, -7 }, // 0x5E '^'
{ 399, 7, 1, 7, 0, 2 }, // 0x5F '_'
{ 400, 3, 2, 4, 0, -8 }, // 0x60 '`'
{ 401, 6, 6, 7, 0, -5 }, // 0x61 'a'
{ 406, 6, 9, 7, 1, -8 }, // 0x62 'b'
{ 413, 6, 6, 7, 0, -5 }, // 0x63 'c'
{ 418, 6, 9, 7, 0, -8 }, // 0x64 'd'
{ 425, 6, 6, 7, 0, -5 }, // 0x65 'e'
{ 430, 4, 9, 4, 0, -8 }, // 0x66 'f'
{ 435, 6, 9, 7, 0, -5 }, // 0x67 'g'
{ 442, 5, 9, 7, 1, -8 }, // 0x68 'h'
{ 448, 1, 9, 3, 1, -8 }, // 0x69 'i'
{ 450, 2, 12, 3, 0, -8 }, // 0x6A 'j'
{ 453, 5, 9, 7, 1, -8 }, // 0x6B 'k'
{ 459, 1, 9, 3, 1, -8 }, // 0x6C 'l'
{ 461, 9, 6, 10, 1, -5 }, // 0x6D 'm'
{ 468, 5, 6, 7, 1, -5 }, // 0x6E 'n'
{ 472, 7, 6, 7, 0, -5 }, // 0x6F 'o'
{ 478, 6, 9, 7, 1, -5 }, // 0x70 'p'
{ 485, 6, 9, 7, 0, -5 }, // 0x71 'q'
{ 492, 3, 6, 5, 1, -5 }, // 0x72 'r'
{ 495, 6, 6, 7, 0, -5 }, // 0x73 's'
{ 500, 4, 8, 4, 0, -7 }, // 0x74 't'
{ 504, 5, 6, 7, 1, -5 }, // 0x75 'u'
{ 508, 6, 6, 7, 0, -5 }, // 0x76 'v'
{ 513, 9, 6, 9, 0, -5 }, // 0x77 'w'
{ 520, 6, 6, 7, 0, -5 }, // 0x78 'x'
{ 525, 6, 9, 7, 0, -5 }, // 0x79 'y'
{ 532, 6, 6, 6, 0, -5 }, // 0x7A 'z'
{ 537, 4, 11, 5, 0, -8 }, // 0x7B '{'
{ 543, 1, 11, 3, 1, -8 }, // 0x7C '|'
{ 545, 3, 11, 5, 1, -8 }, // 0x7D '}'
{ 550, 5, 2, 7, 1, -3 } }; // 0x7E '~'
const GFXfont FreeSansBold6pt7b = {
(uint8_t *)FreeSansBold6pt7bBitmaps,
(GFXglyph *)FreeSansBold6pt7bGlyphs,
0x20, 0x7E, 19 };
// Approx. 1224 bytes

View File

@@ -0,0 +1,197 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
Simple XPT2046 SPI/Bitbang interface for PacoMouseCYD
*/
#include "config.h"
#include "XPT2046.h"
#define Z_THRESHOLD 300
#define MSEC_THRESHOLD 4
XPT2046_TS::XPT2046_TS(uint8_t mosiPin, uint8_t misoPin, uint8_t clkPin, uint8_t csPin) :
_mosiPin(mosiPin), _misoPin(misoPin), _clkPin(clkPin), _csPin(csPin) {
cal = TouchCalibration{0, 4095, 0, 4095, 0}; // other initializations, if required
_msraw = millis();
#ifdef USE_XPT2046_SPI
hspi = new SPIClass(HSPI); // XPT2046 connected to HSPI in CYD 2.4"
hspi->begin();
#endif
}
void XPT2046_TS::begin(uint16_t width, uint16_t height) {
pinMode(_csPin, OUTPUT);
digitalWrite(_csPin, HIGH);
#ifdef USE_XPT2046_BITBANG
pinMode(_clkPin, OUTPUT); // init all pins in bitbang mode only (CYD 2.8")
digitalWrite(_clkPin, LOW);
pinMode(_mosiPin, OUTPUT);
pinMode(_misoPin, INPUT);
#endif
_width = width;
_height = height;
}
void XPT2046_TS::setCalibration(uint16_t xMin, uint16_t xMax, uint16_t yMin, uint16_t yMax) {
cal.xMin = xMin;
cal.xMax = xMax;
cal.yMin = yMin;
cal.yMax = yMax;
}
TouchCalibration XPT2046_TS::getCalibration() {
return cal;
}
void XPT2046_TS::setRotation(uint8_t n) {
cal.rotation = n % 4;
}
bool XPT2046_TS::touched() {
update();
return (_zraw > Z_THRESHOLD);
}
TSPoint XPT2046_TS::getTouch() {
update();
uint16_t x = map(_xraw, cal.xMin, cal.xMax, 0, _width);
uint16_t y = map(_yraw, cal.yMin, cal.yMax, 0, _height);
if ((x >= _width) || (x <= 0) || (y >= _height) || (y <= 0))
_zraw = 0;
return TSPoint{x, y, _zraw};
}
void XPT2046_TS::readData(uint16_t *x, uint16_t *y, uint16_t *z) {
update();
*x = _xraw; // read raw data
*y = _yraw;
*z = _zraw;
}
#ifdef USE_XPT2046_BITBANG
uint16_t XPT2046_TS::readSPI(byte command) {
uint16_t result = 0;
for (int i = 7; i >= 0; i--) {
digitalWrite(_mosiPin, command & (1 << i)); // send command
digitalWrite(_clkPin, HIGH);
delayMicroseconds(7);
digitalWrite(_clkPin, LOW);
delayMicroseconds(7);
}
for (int i = 11; i >= 0; i--) { // read data
digitalWrite(_clkPin, HIGH);
delayMicroseconds(7);
digitalWrite(_clkPin, LOW);
delayMicroseconds(7);
result |= (digitalRead(_misoPin) << i);
}
return result;
}
void XPT2046_TS::update() {
int t;
uint32_t now = millis();
if (now - _msraw < MSEC_THRESHOLD)
return;
digitalWrite(_csPin, LOW);
readSPI(0xB0);
readSPI(0xB0);
readSPI(0xB0);
int z1 = readSPI(0xB0);
_zraw = z1 + 4095;
readSPI(0xC0);
readSPI(0xC0);
readSPI(0xC0);
int z2 = readSPI(0xC0);
_zraw -= z2;
readSPI(0x90);
readSPI(0x90);
readSPI(0x90);
_xraw = readSPI(0x90);
readSPI(0xD0);
readSPI(0xD0);
readSPI(0xD0);
_yraw = readSPI(0xD0);
digitalWrite(_csPin, HIGH);
_msraw = now;
switch (cal.rotation) {
case 0:
t = 4095 - _yraw;
_yraw = _xraw;
_xraw = t;
break;
case 1:
break;
case 2:
t = _xraw;
_xraw = _yraw;
_yraw = 4095 - t;
break;
default:
_xraw = 4095 - _xraw;
_yraw = 4095 - _yraw;
break;
}
}
#endif
#ifdef USE_XPT2046_SPI
void XPT2046_TS::update() {
int t;
uint32_t now = millis();
if (now - _msraw < MSEC_THRESHOLD)
return;
hspi->beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
digitalWrite(_csPin, LOW);
hspi->transfer(0xB0);
hspi->transfer16(0xB0);
hspi->transfer16(0xB0);
hspi->transfer16(0xB0);
int z1 = hspi->transfer16(0xC0) >> 3;
_zraw = z1 + 4095;
hspi->transfer16(0xC0);
hspi->transfer16(0xC0);
hspi->transfer16(0xC0);
int z2 = hspi->transfer16(0x90) >> 3;
_zraw -= z2;
hspi->transfer16(0x90);
hspi->transfer16(0x90);
hspi->transfer16(0x90);
_xraw = hspi->transfer16(0xD0) >> 3;
hspi->transfer16(0xD0);
hspi->transfer16(0xD0);
hspi->transfer16(0xD0);
_yraw = hspi->transfer16(0x0) >> 3;
digitalWrite(_csPin, HIGH);
hspi->endTransaction();
_msraw = now;
switch (cal.rotation) {
case 0:
_xraw = 4095 - _xraw;
break;
case 1:
t = _yraw;
_yraw = _xraw;
_xraw = t;
break;
case 2:
_yraw = 4095 - _yraw;
break;
default:
t = _yraw;
_yraw = 4095 - _xraw;
_xraw = 4095 - t;
break;
}
}
#endif

View File

@@ -0,0 +1,62 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
Simple XPT2046 SPI/Bitbang interface for PacoMouseCYD
*/
#ifndef XPT2046_TS_h
#define XPT2046_TS_h
#include "Arduino.h"
#include "config.h"
#ifdef USE_XPT2046_SPI
#include <SPI.h>
#endif
typedef struct {
uint16_t x;
uint16_t y;
uint16_t z;
} TSPoint;
struct TouchCalibration {
uint16_t xMin;
uint16_t xMax;
uint16_t yMin;
uint16_t yMax;
uint16_t rotation;
};
class XPT2046_TS {
public:
XPT2046_TS(uint8_t mosiPin, uint8_t misoPin, uint8_t clkPin, uint8_t csPin);
void begin(uint16_t width = 240, uint16_t height = 320);
bool touched();
TSPoint getTouch();
void setRotation(uint8_t n);
void setCalibration(uint16_t xMin, uint16_t xMax, uint16_t yMin, uint16_t yMax);
TouchCalibration getCalibration();
void readData(uint16_t *x, uint16_t *y, uint16_t *z);
private:
uint8_t _mosiPin;
uint8_t _misoPin;
uint8_t _clkPin;
uint8_t _csPin;
uint8_t _irqPin;
uint16_t _width;
uint16_t _height;
uint16_t _xraw;
uint16_t _yraw;
uint16_t _zraw;
uint32_t _msraw;
TouchCalibration cal;
#ifdef USE_XPT2046_SPI
SPIClass *hspi = NULL;
#endif
#ifdef USE_XPT2046_BITBANG
uint16_t readSPI(byte command);
#endif
void update();
};
#endif

View File

@@ -0,0 +1,358 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
*/
////////////////////////////////////////////////////////////
// ***** ACCESSORY FIFO *****
////////////////////////////////////////////////////////////
void initFIFO() {
lastInFIFO = 0;
firstOutFIFO = 0;
cntFIFO = 0;
stateFIFO = FIFO_EMPTY;
}
unsigned int readFIFO () {
firstOutFIFO = (firstOutFIFO + 1 ) & 0x1F; // next one (hardcoded)
cntFIFO--;
return (accessoryFIFO[firstOutFIFO]);
}
void writeFIFO (uint16_t FAdr, uint8_t pos) {
lastInFIFO = (lastInFIFO + 1 ) & 0x1F; // next one (hardcoded)
cntFIFO++;
if (pos > 0)
FAdr |= 0x8000;
accessoryFIFO[lastInFIFO] = FAdr; // save in FIFO
}
void sendAccessoryFIFO () {
switch (stateFIFO) {
case FIFO_ACC_CDU: // wait for CDU recharge
case FIFO_EMPTY:
if (cntFIFO > 0) { // activate accessory from FIFO
lastAccessory = readFIFO();
sendAccessory(lastAccessory & 0x7FFF, bitRead(lastAccessory, 15), true);
setTimer(TMR_ACCESSORY, TIME_ACC_ON, TMR_ONESHOT);
stateFIFO = FIFO_ACC_ON;
DEBUG_MSG("Moving acc. %d-%d", lastAccessory & 0x7FFF, lastAccessory >> 15);
}
else
stateFIFO = FIFO_EMPTY;
break;
case FIFO_ACC_ON: // deactivate accessory
sendAccessory(lastAccessory & 0x7FFF, bitRead(lastAccessory, 15), false);
setTimer(TMR_ACCESSORY, TIME_ACC_CDU, TMR_ONESHOT);
stateFIFO = FIFO_ACC_CDU;
break;
}
}
////////////////////////////////////////////////////////////
// ***** ACCESSORY *****
////////////////////////////////////////////////////////////
void moveAccessory(uint16_t FAdr, uint8_t pos) {
writeFIFO(FAdr, pos); // put accessory in FIFO
if (stateFIFO == FIFO_EMPTY) // if not pending accessories, force sending now
sendAccessoryFIFO();
}
void setAccAspect(uint16_t FAdr, uint16_t FAdr2, uint8_t aspect, uint16_t outs) {
uint8_t pos;
pos = aspect * 4;
if (FAdr > 0) {
if (bitRead(outs, pos)) {
moveAccessory(FAdr, 0);
}
else {
if (bitRead(outs, pos + 1))
moveAccessory(FAdr, 1);
}
}
if (FAdr2 > 0) {
if (bitRead(outs, pos + 2)) {
moveAccessory(FAdr2, 0);
}
else {
if (bitRead(outs, pos + 3))
moveAccessory(FAdr2, 1);
}
}
}
////////////////////////////////////////////////////////////
// ***** PANEL *****
////////////////////////////////////////////////////////////
void deleteAccPanelElement(uint8_t pos) {
accPanel[pos].type = ACC_UNDEF;
accPanel[pos].currAspect = 0;
accPanel[pos].addr = 0;
accPanel[pos].addr2 = 0;
accPanel[pos].activeOutput = 0;
accPanel[pos].accName[0] = '\0';
}
void loadDefaultAccPanel() {
uint8_t n;
for (n = 0; n < 16; n++) { // Default panel is all blank with only a keypad button
deleteAccPanelElement(n);
}
accPanel[15].type = ACC_KEYPAD;
}
void updateAccPanel() {
uint8_t n, type, aspect;
snprintf(panelNameBuf, PANEL_LNG + 1, panelNamesBuf[currPanel]);
for (n = 0; n < 16; n++) {
type = accPanel[n].type;
aspect = accPanel[n].currAspect;
snprintf(accNamesBuf[n], ACC_LNG + 1, accPanel[n].accName);
fncData[FNC_ACC0 + n].num = accDef[type].num;
fncData[FNC_ACC0 + n].idIcon = accDef[type].icon[aspect].fncIcon;
fncData[FNC_ACC0 + n].color = accDef[type].icon[aspect].color;
fncData[FNC_ACC0 + n].colorOn = accDef[type].icon[aspect].colorOn;
fncData[FNC_ACC0 + n].backgnd = (type == ACC_UNDEF) ? COLOR_WHITE : COLOR_LIGHTGREY;
buttonData[BUT_ACC_0 + n].backgnd = (type == ACC_UNDEF) ? COLOR_WHITE : COLOR_LIGHTGREY;
}
}
void saveCurrentAspects() {
uint8_t n;
for (n = 0; n < 16; n++)
savedAspect[currPanel][n] = accPanel[n].currAspect;
}
void getLastAspects() {
uint8_t n;
for (n = 0; n < 16; n++)
accPanel[n].currAspect = savedAspect[currPanel][n];
}
void deleteLastAspects() {
uint8_t n;
for (n = 0; n < 16; n++)
accPanel[n].currAspect = 0;
}
void initLastAspects() {
uint8_t i, j;
for (i = 0; i < 16; i++)
for (j = 0; j < 16; j++)
savedAspect[i][j] = 0;;
}
void populateAccPanel() {
loadDefaultAccPanel(); // Load panel data
if (sdDetected) {
if (loadAccPanel(SD))
getLastAspects();
else
deleteLastAspects();
}
else {
if (loadAccPanel(LittleFS))
getLastAspects();
else
deleteLastAspects();
}
updateAccPanel();
}
void accPanelClick(uint8_t pos) {
uint16_t addr, outs;
uint8_t type, aspect;
currPanelAcc = pos;
if (editAccessory) {
paramChild = pos;
type = accPanel[pos].type;
fncData[FNC_ACC_TYPE].num = accDef[type].num;
fncData[FNC_ACC_TYPE].idIcon = accDef[type].icon[0].fncIcon;
fncData[FNC_ACC_TYPE].color = accDef[type].icon[0].color;
fncData[FNC_ACC_TYPE].colorOn = accDef[type].icon[0].colorOn;
encoderValue = type;
encoderMax = ACC_MAX - 1;
openWindow(WIN_ACC_TYPE);
}
else {
type = accPanel[pos].type;
switch (type) {
case ACC_UNDEF:
break;
case ACC_KEYPAD:
openWindow(WIN_ACC_CTRL);
break;
default:
addr = accPanel[pos].addr;
outs = accPanel[pos].activeOutput;
switch (accDef[type].aspects) {
case 2:
aspect = (accPanel[pos].currAspect > 0) ? 0 : 1;
accPanel[pos].currAspect = aspect;
fncData[FNC_ACC0 + pos].idIcon = accDef[type].icon[aspect].fncIcon;
fncData[FNC_ACC0 + pos].color = accDef[type].icon[aspect].color;
fncData[FNC_ACC0 + pos].colorOn = accDef[type].icon[aspect].colorOn;
setAccAspect(addr, 0, aspect, outs);
newEvent(OBJ_BUTTON, BUT_ACC_0 + pos, EVNT_DRAW);
break;
case 3:
currAccAspects = 3;
winData[WIN_ACC_ASPECT].x = 30;
winData[WIN_ACC_ASPECT].w = 180;
buttonData[BUT_ACC_ASPECT0].x = 50;
buttonData[BUT_ACC_ASPECT1].x = 100;
buttonData[BUT_ACC_ASPECT2].x = 150;
fncData[FNC_ASPECT0].x = 54;
fncData[FNC_ASPECT0].idIcon = accDef[type].icon[0].fncIcon;
fncData[FNC_ASPECT0].color = accDef[type].icon[0].color;
fncData[FNC_ASPECT0].colorOn = accDef[type].icon[0].colorOn;
fncData[FNC_ASPECT1].x = 104;
fncData[FNC_ASPECT1].idIcon = accDef[type].icon[1].fncIcon;
fncData[FNC_ASPECT1].color = accDef[type].icon[1].color;
fncData[FNC_ASPECT1].colorOn = accDef[type].icon[1].colorOn;
fncData[FNC_ASPECT2].x = 154;
fncData[FNC_ASPECT2].idIcon = accDef[type].icon[2].fncIcon;
fncData[FNC_ASPECT2].color = accDef[type].icon[2].color;
fncData[FNC_ASPECT2].colorOn = accDef[type].icon[2].colorOn;
openWindow(WIN_ACC_ASPECT);
break;
case 4:
currAccAspects = 4;
winData[WIN_ACC_ASPECT].x = 5;
winData[WIN_ACC_ASPECT].w = 230;
buttonData[BUT_ACC_ASPECT0].x = 25;
buttonData[BUT_ACC_ASPECT1].x = 75;
buttonData[BUT_ACC_ASPECT2].x = 125;
buttonData[BUT_ACC_ASPECT3].x = 175;
fncData[FNC_ASPECT0].x = 29;
fncData[FNC_ASPECT0].idIcon = accDef[type].icon[0].fncIcon;
fncData[FNC_ASPECT0].color = accDef[type].icon[0].color;
fncData[FNC_ASPECT0].colorOn = accDef[type].icon[0].colorOn;
fncData[FNC_ASPECT1].x = 79;
fncData[FNC_ASPECT1].idIcon = accDef[type].icon[1].fncIcon;
fncData[FNC_ASPECT1].color = accDef[type].icon[1].color;
fncData[FNC_ASPECT1].colorOn = accDef[type].icon[1].colorOn;
fncData[FNC_ASPECT2].x = 129;
fncData[FNC_ASPECT2].idIcon = accDef[type].icon[2].fncIcon;
fncData[FNC_ASPECT2].color = accDef[type].icon[2].color;
fncData[FNC_ASPECT2].colorOn = accDef[type].icon[2].colorOn;
fncData[FNC_ASPECT3].x = 179;
fncData[FNC_ASPECT3].idIcon = accDef[type].icon[3].fncIcon;
fncData[FNC_ASPECT3].color = accDef[type].icon[3].color;
fncData[FNC_ASPECT3].colorOn = accDef[type].icon[3].colorOn;
openWindow(WIN_ACC_ASPECT);
break;
default:
buttonData[BUT_ACC_0 + pos].backgnd = COLOR_BLACK;
drawObject(OBJ_BUTTON, BUT_ACC_0 + pos);
buttonData[BUT_ACC_0 + pos].backgnd = COLOR_LIGHTGREY;
setAccAspect(addr, 0, 0, outs);
newEvent(OBJ_BUTTON, BUT_ACC_0 + pos, EVNT_DRAW);
delay(80);
break;
}
break;
}
}
}
void accAspectClick(uint8_t aspect) {
uint16_t addr, addr2, outs;
uint8_t type;
type = accPanel[currPanelAcc].type;
accPanel[currPanelAcc].currAspect = aspect;
fncData[FNC_ACC0 + currPanelAcc].idIcon = accDef[type].icon[aspect].fncIcon;
fncData[FNC_ACC0 + currPanelAcc].color = accDef[type].icon[aspect].color;
fncData[FNC_ACC0 + currPanelAcc].colorOn = accDef[type].icon[aspect].colorOn;
addr = accPanel[currPanelAcc].addr;
addr2 = accPanel[currPanelAcc].addr2;
outs = accPanel[currPanelAcc].activeOutput;
setAccAspect(addr, addr2, aspect, outs);
}
void accTypeClick() {
uint8_t index, n;
index = encoderValue;
switch (index) {
case ACC_UNDEF:
alertWindow(ERR_ASK_SURE);
break;
case ACC_KEYPAD:
editAccessory = false;
winData[WIN_ACCESSORY].backgnd = COLOR_WHITE;
deleteAccPanelElement(paramChild);
accPanel[paramChild].type = ACC_KEYPAD;
updateAccPanel();
updateSpeedHID(); // set encoder
closeWindow(WIN_ACC_TYPE);
break;
default:
if (index != accPanel[paramChild].type) {
currAccEdit.type = (accType)index;
currAccEdit.addr = 0;
currAccEdit.addr2 = 0;
currAccEdit.currAspect = 0;
currAccEdit.activeOutput = accOutDefault[index];
currAccEdit.accName[0] = '\0';
}
else {
currAccEdit = accPanel[paramChild];
}
snprintf(accKeybName, ACC_LNG + 1, currAccEdit.accName);
snprintf(accKeybAddr1, ADDR_LNG + 1, "%d", currAccEdit.addr);
snprintf(accKeybAddr2, ADDR_LNG + 1, "%d", currAccEdit.addr2);
for (n = 0; n < 4; n++) {
fncData[FNC_EDIT_ASPECT0 + n].idIcon = accDef[index].icon[n].fncIcon;
fncData[FNC_EDIT_ASPECT0 + n].color = accDef[index].icon[n].color;
fncData[FNC_EDIT_ASPECT0 + n].colorOn = accDef[index].icon[n].colorOn;
}
accOutUpdate();
closeWindow(WIN_ACC_TYPE);
openWindow(WIN_ACC_EDIT);
break;
}
}
void accOutUpdate() {
uint8_t n;
for (n = 0; n < 16; n += 2) {
buttonData[BUT_ACC_OUT0 + n].backgnd = bitRead(currAccEdit.activeOutput, n) ? COLOR_RED : COLOR_LIGHTBLACK;
buttonData[BUT_ACC_OUT0 + n + 1].backgnd = bitRead(currAccEdit.activeOutput, n + 1) ? COLOR_GREEN : COLOR_LIGHTBLACK;
}
}
void accOutClick(uint8_t out) {
uint8_t outR, outG;
outR = out & 0xFE;
outG = out | 0x01;
currAccEdit.activeOutput ^= bit(out);
if (bitRead(out, 0)) { // green
if (bitRead(currAccEdit.activeOutput, outG))
bitClear(currAccEdit.activeOutput, outR);
}
else { // red
if (bitRead(currAccEdit.activeOutput, outR))
bitClear(currAccEdit.activeOutput, outG);
}
accOutUpdate();
newEvent(OBJ_BUTTON, BUT_ACC_OUT0 + outR, EVNT_DRAW);
newEvent(OBJ_BUTTON, BUT_ACC_OUT0 + outG, EVNT_DRAW);
}
void updateAccChange() {
accPanel[paramChild] = currAccEdit;
updateAccPanel();
accPanelChanged = true;
}

View File

@@ -0,0 +1,184 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
*/
#ifndef PACOMOUSECYD_CFG_H
#define PACOMOUSECYD_CFG_H
#define CYD_TFT_28 0 // Cheap Yellow Display 2.8"
#define CYD_TFT_24 1 // Cheap Yellow Display 2.4"
#define CYD_TFT_32 2 // Cheap Yellow Display 3.2"
#define CYD_USER_DEFINED 3 // User defined board
#define PRESENT 1
#define UNUSED 0
#define MODE_SPI 0
#define MODE_BITBANG 1
////////////////////////////////////////////////////////////
// ***** USER OPTIONS *****
////////////////////////////////////////////////////////////
// Seleccione la version hardware del CYD (Cheap Yellow Display) - Select the hardware version of CYD (Cheap Yellow Display): CYD_TFT_28 / CYD_TFT_24 / CYD_TFT_32 / CYD_USER_DEFINED
// Use el archivo User_Setup.h correcto para la libreria TFT_eSPI - Use the correct User_Setup.h file for library TFT_eSPI
#define CYD_HW_VERSION CYD_TFT_28
// Max. locomotoras guardadas en stack (hasta 254) - Max. locomotives saved in stack (up to 254):
#define LOCOS_IN_STACK 100
// Delimitador en fichero CSV - CSV file delimiter: ';' / ','
#define CSV_FILE_DELIMITER ';'
#if (CYD_HW_VERSION == CYD_USER_DEFINED)
////////////////////////////////////////////////////////////
// ***** USER DEFINED HARDWARE *****
////////////////////////////////////////////////////////////
// Seleccione el modo de acceso al chip XPT2046 - Select XPT2046 chip access mode : MODE_SPI / MODE_BITBANG
#define XPT_MODE MODE_SPI
// Seleccione rotacion de la pantalla tactil - Select Touchscreen rotation: 0 / 1 / 2 / 3
#define XPT_ROTATION 3
// Touchscreen
#define XPT2046_IRQ 36 // T_IRQ
#define XPT2046_MOSI 13 // T_DIN
#define XPT2046_MISO 12 // T_OUT
#define XPT2046_CLK 14 // T_CLK
#define XPT2046_CS 33 // T_CS
// Seleccione si usa el LED RGB - Select if use the RGB LED: PRESENT / UNUSED
#define USE_RGB_LED PRESENT
//RGB LED Pins
#define RGB_LED_R 4
#define RGB_LED_G 17
#define RGB_LED_B 16
//SD Pins
#define SD_CS 5
// Encoder Pins
#define ENCODER_A 22
#define ENCODER_B 21
#define ENCODER_SW 35
#endif
////////////////////////////////////////////////////////////
// ***** END OF USER DEFINED HARDWARE *****
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
// ***** END OF USER OPTIONS *****
////////////////////////////////////////////////////////////
#if (CYD_HW_VERSION == CYD_TFT_28)
#define USE_CYD_28 1 // Cheap Yellow Display 2.8" (2432S028R)
#endif
#if (CYD_HW_VERSION == CYD_TFT_24)
#define USE_CYD_24 1 // Cheap Yellow Display 2.4" (2432S024R)
#endif
#if (CYD_HW_VERSION == CYD_TFT_32)
#define USE_CYD_24 1 // Cheap Yellow Display 3.2" (2432S032R)
#endif
#if (CYD_HW_VERSION == CYD_USER_DEFINED)
#if (XPT_MODE == MODE_SPI) // Cheap Yellow Display other type
#define USE_XPT2046_SPI
#endif
#if (XPT_MODE == MODE_BITBANG)
#define USE_XPT2046_BITBANG
#endif
#endif
#if defined(USE_CYD_28) && defined(USE_CYD_24)
#error Seleccione solo un tipo de CYD (Cheap Yellow Display) - Select only one type of CYD (Cheap Yellow Display)
#endif
#define USER_MIN_BL 64 // User min backlight
#define SYS_MIN_BL 32 // System inactivity backlight
#ifdef USE_CYD_28
// Touchscreen pins
#define XPT2046_IRQ 36 // T_IRQ
#define XPT2046_MOSI 32 // T_DIN
#define XPT2046_MISO 39 // T_OUT
#define XPT2046_CLK 25 // T_CLK
#define XPT2046_CS 33 // T_CS
#define USE_XPT2046_BITBANG
#define XPT_ROTATION 0
/*
// I2C pins
#define I2C_SDA 27
#define I2C_SCL 22
*/
//RGB LED
#define RGB_LED_R 4
#define RGB_LED_G 16
#define RGB_LED_B 17
#define USE_RGB_LED PRESENT
//SD Pins
#define SD_CS 5
// Encoder
#define ENCODER_A 22
#define ENCODER_B 27
#define ENCODER_SW 35
#endif
#ifdef USE_CYD_24
// Touchscreen pins
#define XPT2046_IRQ 36 // T_IRQ
#define XPT2046_MOSI 13 // T_DIN
#define XPT2046_MISO 12 // T_OUT
#define XPT2046_CLK 14 // T_CLK
#define XPT2046_CS 33 // T_CS
#define USE_XPT2046_SPI
#define XPT_ROTATION 0
/*
// I2C pins
#define I2C_SDA 21
#define I2C_SCL 22
*/
//RGB LED
#define RGB_LED_R 4
#define RGB_LED_G 17
#define RGB_LED_B 16
#define USE_RGB_LED PRESENT
//SD Pins
#define SD_CS 5
// Encoder
#define ENCODER_A 22
#define ENCODER_B 21
#define ENCODER_SW 35
#endif
#endif

View File

@@ -0,0 +1,956 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
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.
*/
////////////////////////////////////////////////////////////
// ***** ECoS SOPORTE *****
////////////////////////////////////////////////////////////
void sendMsgECOS (char *buf) {
bool recvAnswer;
uint32_t timeoutECoS;
Client.write(buf);
#ifdef DEBUG
Serial.print(F("Send: "));
Serial.println(buf);
#endif
timeoutECoS = millis();
recvAnswer = false;
while ((millis() - timeoutECoS < 50) && (!recvAnswer)) // wait answer for 50ms
recvAnswer = ECoSProcess();
}
void requestViews () {
snprintf(cmd, 64, "get(%d, info)\n", ID_ECOS); // ECoS info
sendMsgECOS(cmd);
snprintf(cmd, 64, "request(%d, view)\n", ID_ECOS); // ECoS manager (power)
sendMsgECOS(cmd);
snprintf(cmd, 64, "request(%d, view)\n", ID_LOKMANAGER); // Lok manager
sendMsgECOS(cmd);
snprintf(cmd, 64, "request(%d, view)\n", ID_S88FEEDBACK); // S88 feedback
sendMsgECOS(cmd);
getStatusECoS();
}
void getStatusECoS() {
snprintf(cmd, 64, "get(%d, status)\n", ID_ECOS); // Power status
sendMsgECOS(cmd);
}
void requestLocoList() {
snprintf(cmd, 64, "queryObjects(%d, addr, name)\n", ID_LOKMANAGER); // only address and name, protocol not needed at the moment
sendMsgECOS(cmd);
}
void infoLocomotoraECoS (unsigned int ID) {
byte n;
if (ID > 999) {
bitClear(locoData[myLocoData].mySteps, 3);
snprintf(cmd, 64, "request(%d, view, control[events])\n", ID); // View Locomotive data updates
sendMsgECOS(cmd);
snprintf(cmd, 64, "get(%d, speed, dir, speedstep)\n", ID); // Speed, dir & steps
sendMsgECOS(cmd);
for (n = 0; n < 29; n++) {
snprintf(cmd, 64, "get(%d, func[%d])\n", ID, n); // Functions
sendMsgECOS(cmd);
if (appVer > 2)
snprintf(cmd, 64, "get(%d, funcicon[%d])\n", ID, n); // Icon functions
else
snprintf(cmd, 64, "get(%d, funcsymbol[%d])\n", ID, n); // Icon functions
sendMsgECOS(cmd);
}
setTimer (TMR_FNC_ECOS, 10, TMR_ONESHOT);
}
}
void releaseLocoECoS() {
if (locoData[myLocoData].myLocoID) {
snprintf(cmd, 64, "release(%d, control)\n", locoData[myLocoData].myLocoID); // release control
sendMsgECOS(cmd);
snprintf(cmd, 64, "release(%d, view)\n", locoData[myLocoData].myLocoID); // release updates
sendMsgECOS(cmd);
}
}
void checkControlLoco() {
if (bitRead(locoData[myLocoData].mySteps, 3)) {
bitClear(locoData[myLocoData].mySteps, 3);
snprintf(cmd, 64, "request(%d, control[events], force)\n", locoData[myLocoData].myLocoID); // force control
sendMsgECOS(cmd);
}
}
void changeDirectionECoS() {
checkControlLoco();
snprintf(cmd, 64, "set(%d, dir[%d])\n", locoData[myLocoData].myLocoID, (locoData[myLocoData].myDir & 0x80) ? 0 : 1); // direction (1=rückwärts).
sendMsgECOS(cmd);
}
void locoOperationSpeedECoS() {
checkControlLoco();
snprintf(cmd, 64, "set(%d, speed[%d])\n", locoData[myLocoData].myLocoID, locoData[myLocoData].mySpeed); // Speed
sendMsgECOS(cmd);
}
byte getCurrentStepECoS() {
return (mySpeedStep);
}
void funcOperationsECoS (byte fnc) {
byte stat;
checkControlLoco();
stat = (bitRead(locoData[myLocoData].myFunc.Bits, fnc)) ? 1 : 0;
snprintf(cmd, 64, "set(%d, func[%d,%d])\n", locoData[myLocoData].myLocoID, fnc, stat); // Function
sendMsgECOS(cmd);
}
void setAccessoryECoS(unsigned int FAdr, int pair, bool activate) {
char pos;
if (activate) {
pos = (pair > 0) ? 'g' : 'r';
snprintf(cmd, 64, "set(%d, switch[%d%c])\n", ID_SWMANAGER, FAdr, pos); // Directly setting a port.
sendMsgECOS(cmd);
}
}
void resumeOperationsECoS () {
snprintf(cmd, 64, "set(%d, go)\n", ID_ECOS); // Track on
sendMsgECOS(cmd);
}
void emergencyOffECoS() {
snprintf(cmd, 64, "set(%d, stop)\n", ID_ECOS); // Track off
sendMsgECOS(cmd);
}
void showTrkECoS() {
if (csStatus > 0) {
stopTimer (TMR_POWER);
iconData[ICON_POWER].color = COLOR_GREEN;
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
newEvent(OBJ_ICON, ICON_POWER, EVNT_DRAW);
if (isWindow(WIN_STA_PLAY)) {
fncData[FNC_STA_RAYO].state = false;
newEvent(OBJ_FNC, FNC_STA_RAYO, EVNT_DRAW);
}
if (isWindow(WIN_ALERT)) {
switch (errType) {
case ERR_SERV:
case ERR_STOP:
case ERR_CV:
closeWindow(WIN_ALERT);
break;
}
}
}
else {
iconData[ICON_POWER].color = COLOR_RED; // Power off
setTimer (TMR_POWER, 5, TMR_PERIODIC); // Flash power icon
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
newEvent(OBJ_ICON, ICON_POWER, EVNT_DRAW);
if (isWindow(WIN_STA_PLAY)) {
fncData[FNC_STA_RAYO].state = true;
newEvent(OBJ_FNC, FNC_STA_RAYO, EVNT_DRAW);
}
}
}
void readCVECoS (unsigned int adr, byte stepPrg) {
if (!modeProg) { // Read only in Direct mode
snprintf(cmd, 64, "request(%d, view)\n", ID_PRGMANAGER); // Programming manager
sendMsgECOS(cmd);
requestedCV = true;
snprintf(cmd, 64, "set(%d, mode[readdccdirect], cv[%d])\n", ID_PRGMANAGER, adr);
sendMsgECOS(cmd);
}
progStepCV = stepPrg;
}
void writeCVECoS (unsigned int adr, unsigned int data, byte stepPrg) {
if (modeProg) {
snprintf(cmd, 64, "request(%d, view)\n", ID_POMMANAGER); // PoM Programming manager
sendMsgECOS(cmd);
snprintf(cmd, 64, "set(%d, mode[writedccpomloco], addr[%d], cv[%d,%d])\n", ID_POMMANAGER, locoData[myLocoData].myAddr.address, adr, data); // Operations Mode Programming byte mode write request
}
else {
snprintf(cmd, 64, "request(%d,view)\n", ID_PRGMANAGER); // Programming manager
sendMsgECOS(cmd);
snprintf(cmd, 64, "set(%d, mode[writedccdirect], cv[%d,%d])\n", ID_PRGMANAGER, adr, data);
}
requestedCV = true;
sendMsgECOS(cmd);
progStepCV = stepPrg;
DEBUG_MSG("Write CV%d = %d", adr, data);
}
void exitProgrammingECoS() {
if (requestedCV) {
snprintf(cmd, 64, "release(%d,view)\n", ID_PRGMANAGER); // Programming manager
sendMsgECOS(cmd);
snprintf(cmd, 64, "release(%d, view)\n", ID_POMMANAGER); // PoM Programming manager
sendMsgECOS(cmd);
requestedCV = false;
}
}
////////////////////////////////////////////////////////////
// ***** ECoS DECODE *****
////////////////////////////////////////////////////////////
bool ECoSProcess() {
char chr;
bool rcvMsg;
rcvMsg = false;
while (Client.available()) {
chr = Client.read();
if (chr == '\n') {
if (inputLength > 0) {
inputBuffer[inputLength] = 0;
rcvMsg = ECoSDecode();
}
inputLength = 0;
}
else {
inputBuffer[inputLength++] = chr;
if (inputLength >= BUF_LNG) // line too long, discard
inputLength = 0;
}
}
yield();
if (progFinished) { // fin de lectura/programacion CV
progFinished = false;
endProg();
}
return rcvMsg;
}
//============= Scanning ==================
// Simplified scanning based on C compiler method. https://github.com/DoctorWkt/acwj
char getChar () { // get a chr form input buffer
return inputBuffer[posFile++];
}
void putBack(char c) { // Put back an unwanted character
putBackChr = c;
posFile--; // set reading position back
}
char next() { // Get the next character from the input stream.
char c;
if (putBackChr) { // Use the character put back if there is one
c = putBackChr;
putBackChr = 0;
posFile++;
}
else
c = getChar(); // Read from input stream
return c;
}
char skip() { // Skip past input that we don't need to deal with, i.e. whitespace, newlines. Return the first character we do need to deal with.
char c;
c = next();
while (' ' == c || '\t' == c || '\n' == c || '\r' == c) {
c = next();
}
return (c);
}
void discardLine() { // ignore rest of the line
putBackChr = 0;
inputBuffer[posFile] = 0;
}
int scanInt(char c) { // Scan and return an integer literal value from the input stream.
int val;
val = 0;
while (isDigit(c)) { // Convert each character into an int value
val = val * 10 + (c - '0');
c = next();
}
putBack(c); // We hit a non-integer character, put it back.
return val;
}
int scanIdent(int c, char *buf, int lim) { // Scan an identifier from the input file and store it in buf[]. Return the identifier's length
int i = 0;
while (isalpha(c) || isdigit(c) || (c == '-') || (c == '_')) { // Allow digits, alpha and underscore -
if (lim - 1 == i) { // Error if we hit the identifier length limit, else append to buf[] and get next character
return (0);
} else if (i < lim - 1) {
buf[i++] = c;
}
c = next();
}
putBack(c); // We hit a non-valid character, put it back. NUL-terminate the buf[] and return the length
buf[i] = '\0';
return (i);
}
int scanStr (char *buf) { // Scan in a string literal from the input file, and store it in buf[]. Return the length of the string.
int i, c;
for (i = 0; i < TEXTLEN - 1; i++) { // Loop while we have enough buffer space
// Get the next char and append to buf
// Return when we hit the ending double quote
if ((c = next()) == '"') {
buf[i] = 0;
return (i);
}
buf[i] = c;
}
// Ran out of buf[] space
return (0);
}
int ident2HEX (char *buf, int *value) { // convert an idetifier (xNNNN) to hex value
int val, n;
char c;
val = 0;
n = 1;
if (buf[0] == 'x') {
while (buf[n]) {
c = buf[n++];
val *= 16;
if (isDigit(c)) {
val += (c - '0');
}
else {
c &= 0xDF;
val += ((c - 'A') + 10);
}
}
*value = val;
return 1;
}
return 0;
}
// Given a word from the input, return the matching keyword token number or 0 if it's not a keyword.
// Switch on the first letter so that we don't have to waste time strcmp()ing against all the keywords.
int keyword(char *s) {
switch (*s) {
case 'R':
if (!strcmp(s, "REPLY"))
return (T_REPLY);
break;
case 'E':
if (!strcmp(s, "END"))
return (T_END);
if (!strcmp(s, "EVENT"))
return (T_EVENT);
if (!strcmp(s, "ECoS2"))
return (T_ECOS2);
if (!strcmp(s, "ECoS"))
return (T_ECOS);
break;
case 'O':
if (!strcmp(s, "OK"))
return (T_OK);
break;
case 'C':
if (!strcmp(s, "CONTROL_LOST"))
return (T_LOST);
if (!strcmp(s, "CentralStation"))
return (T_CS1);
break;
case 'G':
if (!strcmp(s, "GO"))
return (T_GO);
break;
case 'S':
if (!strcmp(s, "STOP"))
return (T_STOP);
if (!strcmp(s, "SHUTDOWN"))
return (T_SHUTDWN);
break;
case 'A':
if (!strcmp(s, "ApplicationVersion"))
return (T_APPV);
break;
case 'a':
if (!strcmp(s, "addr"))
return (T_ADDR);
break;
case 'c':
if (!strcmp(s, "control"))
return (T_CONTROL);
if (!strcmp(s, "cv"))
return (T_CV);
break;
case 'd':
if (!strcmp(s, "dir"))
return (T_DIR);
break;
case 'e':
if (!strcmp(s, "error"))
return (T_ERROR);
break;
case 'f':
if (!strcmp(s, "funcsymbol")) // ECoS replies 'funcsymbol'
return (T_FSYMBOL);
if (!strcmp(s, "funcsymb")) // CS1 replies 'funcsymb'
return (T_FSYMB);
if (!strcmp(s, "funcicon"))
return (T_FICON);
if (!strcmp(s, "func"))
return (T_FUNC);
if (!strcmp(s, "force"))
return (T_FORCE);
break;
case 'g':
if (!strcmp(s, "get"))
return (T_GET);
break;
case 'm':
if (!strcmp(s, "msg"))
return (T_MSG);
break;
case 'n':
if (!strcmp(s, "name"))
return (T_NAME);
break;
case 'o':
if (!strcmp(s, "ok"))
return (T_CVOK);
if (!strcmp(s, "other"))
return (T_OTHER);
break;
case 'p':
if (!strcmp(s, "protocol"))
return (T_PROT);
break;
case 'q':
if (!strcmp(s, "queryObjects"))
return (T_QOBJ);
break;
case 'r':
if (!strcmp(s, "request"))
return (T_REQ);
if (!strcmp(s, "release"))
return (T_RELEASE);
break;
case 's':
if (!strcmp(s, "status2"))
return (T_STATUS2);
if (!strcmp(s, "status"))
return (T_STATUS);
if (!strcmp(s, "state"))
return (T_STATE);
if (!strcmp(s, "speedstep"))
return (T_STEPS);
if (!strcmp(s, "speed"))
return (T_SPEED);
if (!strcmp(s, "set"))
return (T_SET);
if (!strcmp(s, "switch"))
return (T_SWITCH);
break;
case 'v':
if (!strcmp(s, "view"))
return (T_VIEW);
break;
}
return (0);
}
//=========== Lexical ===============
int scan(struct token *t) { // Scan and return the next token found in the input. Return 1 if token valid, 0 if no tokens left.
char c;
c = skip(); // Skip whitespace
switch (c) { // Determine the token based on the input character
case 0: // EOF
return (0);
case '(':
t->token = T_LPARENT;
t->intvalue = posFile;
break;
case ')':
t->token = T_RPARENT;
t->intvalue = posFile;
break;
case '[':
t->token = T_LBRACKET;
t->intvalue = posFile;
break;
case ']':
t->token = T_RBRACKET;
t->intvalue = posFile;
break;
case '<':
t->token = T_START;
t->intvalue = posFile;
break;
case '>':
t->token = T_ENDB;
t->intvalue = posFile;
break;
case ',':
t->token = T_COMMA;
t->intvalue = posFile;
break;
case '#':
t->token = T_NULL;
t->intvalue = posFile;
discardLine();
break;
case '"':
scanStr(Text);
t->token = T_STRLIT;
break;
default:
if (isDigit(c)) { // If it's a digit, scan the literal integer value in
t->intvalue = scanInt(c);
t->token = T_INTLIT;
break;
}
if (isAlpha(c)) {
scanIdent(c, Text, TEXTLEN);
tokenType = keyword(Text);
if (tokenType) {
t->token = tokenType;
break;
}
t->token = T_IDENT; // Not a recognised keyword, so it must be an identifier
t->intvalue = Text[0];
break;
}
return (0);
break;
}
//DEBUG_MSG("Token found: %s", tokstr[T.token]);
return (1); // We found a token
}
//----------------------------------------------------------
bool ECoSDecode() {
bool endMsg;
#ifdef DEBUG
Serial.print(F("Recv: "));
Serial.println(inputBuffer);
#endif
posFile = 0;
putBackChr = 0;
endMsg = false;
while (scan(&T)) {
switch (msgDecodePhase) {
case MSG_WAIT: // wait '<' for start of msg
if (T.token == T_START)
msgDecodePhase = MSG_START;
break;
case MSG_START:
switch (T.token) {
case T_REPLY: // receiving a REPLY
msgDecodePhase = MSG_REPLY;
break;
case T_EVENT: // receiving an EVENT
msgDecodePhase = MSG_EVENT;
break;
case T_END: // receiving END
msgDecodePhase = MSG_END;
break;
default:
msgDecodePhase = MSG_WAIT; // others not supported
break;
}
idManager = 0;
break;
case MSG_END:
if (T.token == T_INTLIT) { // get the error code
errCode = T.intvalue;
discardLine();
msgDecodePhase = MSG_WAIT; // discard rest of message
switch (errCode) {
case 25: // NERROR_NOCONTROL
bitSet(locoData[myLocoData].mySteps, 3);
break;
case 15: // NERROR_UNKNOWNID
if (requestedCV) {
if (progStepCV != PRG_IDLE) {
CVdata = 0x0600;
progFinished = true;
}
}
break;
}
DEBUG_MSG("END Error code: %d", errCode);
}
endMsg = true;
break;
case MSG_EVENT:
if (T.token == T_INTLIT) // get the event manager
idManager = T.intvalue;
discardLine();
msgDecodePhase = MSG_EVENTBODY; // discard rest of line
DEBUG_MSG("Manager ID: %d", idManager);
if ((idManager == ID_PRGMANAGER) || (idManager == ID_POMMANAGER)) {
lastNumValue = 0x0600;
}
break;
case MSG_REPLY: // <REPLY get(1,status)>
idCommand = T.token; // get ...
scan(&T); // (
scan(&T); // id manager
if (T.token == T_INTLIT) {
idManager = T.intvalue;
//DEBUG_MSG("Reply: %s id: %d", tokstr[idCommand], idManager);
if ((idManager == ID_LOKMANAGER) && (idCommand == T_QOBJ)) { // list of loks
numLoks = 0;
initLocos();
DEBUG_MSG("Roster list cleared");
}
/*
if ((idManager == ID_SWMANAGER) && (idCommand == T_GET)) { // info of turnout <REPLY get(11, switch[12g])>
//DEBUG_MSG("Switch manager get");
scan(&T); // ,
scan(&T); // switch
if (T.token == T_SWITCH) {
//DEBUG_MSG("Get turnout");
scan(&T); // [
scan(&T); // 12g
if (T.token == T_INTLIT) // 12
scan(&T);
if (T.token == T_IDENT) { // Text is "g" or could be "DCC12g"
lastTxtChar = Text[strlen(Text) - 1];
DEBUG_MSG("Info turnout pos: %s - %c", Text, lastTxtChar);
}
}
}
*/
}
discardLine();
msgDecodePhase = MSG_REPLYBODY;
break;
case MSG_EVENTBODY:
switch (T.token) {
case T_START:
msgDecodePhase = MSG_START; // End of body
break;
case T_INTLIT:
switch (idManager) { // id
case ID_ECOS: // ECoS events
decodeEventECoS();
break;
case ID_PRGMANAGER: // Programming events
case ID_POMMANAGER:
decodeEventCV(); // decodes CV answer
break;
default:
if (idManager == locoData[myLocoData].myLocoID) { // current loco events
if (T.intvalue == locoData[myLocoData].myLocoID)
decodeReplyLoco();
}
else {
/*
if ((idManager >= ID_S88FEEDBACK) && (idManager < (ID_S88FEEDBACK + 32))) // S88 events
decodeEventS88Feedback();
else
*/
discardLine();
}
break;
}
break;
default: // other types of start of line not supported
discardLine();
break;
}
break;
case MSG_REPLYBODY:
switch (T.token) {
case T_START:
msgDecodePhase = MSG_START; // End of body
break;
case T_INTLIT:
switch (idManager) { // id
case ID_ECOS: // decodes answer to: get(1,...) / request
if (idCommand == T_GET) {
decodeEventECoS();
}
else
discardLine();
break;
case ID_LOKMANAGER: // decodes answer to: queryObjects(10,...)
decodeLokManager();
break;
case ID_SWMANAGER:
if (idCommand == T_GET) { // decodes answer to: get(11,..)
//decodeSwitchManager();
}
else
discardLine();
break;
default:
/*
if ((idManager >= ID_S88FEEDBACK) && (idManager < (ID_S88FEEDBACK + 32)))
decodeEventS88Feedback();
*/
if (idManager == locoData[myLocoData].myLocoID)
decodeReplyLoco(); // decodes answer to: get(1xxx,...) / set..
else
discardLine();
break;
}
break;
default: // other types of start of line not supported
discardLine();
break;
}
break;
}
}
return endMsg;
}
void decodeEventECoS () {
if (T.intvalue == ID_ECOS) {
scan(&T);
switch (T.token) {
case T_STATUS: // 1 status[GO]
scan(&T); // [
scan(&T); // GO / STOP / SHUTDOWN
if (T.token == T_GO) {
csStatus = 1;
showTrkECoS();
DEBUG_MSG("Power On");
}
else {
csStatus = 0;
showTrkECoS();
DEBUG_MSG("Power Off");
}
break;
case T_APPV: // 1 ApplicationVersion[2.0.4]
scan(&T);
scan(&T);
if (T.token == T_INTLIT) {
appVer = T.intvalue;
DEBUG_MSG("Version: %d", appVer)
}
break;
/*
case T_CS1: // 1 CentralStation
case T_ECOS: // 1 ECoS
case T_ECOS2: // 1 ECoS2
typeCmdStation = T.token;
DEBUG_MSG("CS Type: %s", tokstr[typeCmdStation]);
break;
*/
}
discardLine();
}
}
void decodeEventCV() {
while (scan(&T)) {
switch (T.token) {
case T_INTLIT:
lastNumValue = T.intvalue;
break;
case T_CVOK:
CVdata = lastNumValue;
progFinished = true;
discardLine();
break;
case T_ERROR:
CVdata = 0x0600;
progFinished = true;
discardLine();
break;
}
}
}
void parseLokAddrName(int pos) {
scan(&T); // 1003 addr[78] name["W. K"]
switch (T.token) {
case T_ADDR:
scan(&T);
scan(&T);
if (T.token == T_INTLIT)
locoData[pos].myAddr.address = T.intvalue;
scan(&T);
DEBUG_MSG("Addr: %d", locoData[pos].myAddr.address);
break;
case T_NAME:
scan(&T);
scan(&T);
if (T.token == T_STRLIT)
snprintf(locoData[pos].myName, NAME_LNG + 1, "%s", Text);
scan(&T);
DEBUG_MSG("Name: %s", locoData[pos].myName);
break;
}
}
void decodeLokManager () {
int id, fnc, num;
switch (idCommand) {
case T_QOBJ:
if (numLoks < LOCOS_IN_STACK) {
locoData[numLoks].myLocoID = T.intvalue; // 1003 addr[78] name["W. K"]
DEBUG_MSG("ID: %d", locoData[numLoks].myLocoID);
parseLokAddrName(numLoks); // addr
parseLokAddrName(numLoks); // name
pushLoco(locoData[numLoks].myLocoID); //.myAddr.address);
numLoks++;
}
discardLine();
break;
default:
discardLine();
break;
}
}
void decodeReplyLoco() {
int numFunc;
scan(&T);
switch (T.token) { // <REPLY get(1xxx,... )> / <EVENT 1xxx>
case T_MSG: // 1018 msg[CONTROL_LOST]
scan(&T);
scan(&T);
if (T.token == T_LOST) {
bitSet(locoData[myLocoData].mySteps, 3);
DEBUG_MSG("Lost control of %d", locoData[myLocoData].myLocoID);
}
break;
case T_CONTROL: // 1000 control[other]
scan(&T);
scan(&T);
if (T.token == T_OTHER) {
bitSet(locoData[myLocoData].mySteps, 3);
DEBUG_MSG("Control %d by other", locoData[myLocoData].myLocoID);
}
break;
case T_SPEED: // 1000 speed[6]
scan(&T);
scan(&T);
if (T.token == T_INTLIT) {
locoData[myLocoData].mySpeed = T.intvalue;
updateSpeedHID();
DEBUG_MSG("Speed: %d", T.intvalue);
}
break;
case T_STEPS: // 1000 speedstep[2]
scan(&T);
scan(&T);
if (T.token == T_INTLIT) {
mySpeedStep = T.intvalue;
DEBUG_MSG("Steps: %d", T.intvalue);
}
break;
case T_DIR: // 1000 dir[0]
scan(&T);
scan(&T);
if (T.token == T_INTLIT) {
locoData[myLocoData].myDir = (T.intvalue > 0) ? 0 : 0x80;
updateSpeedDir();
DEBUG_MSG("Dir: %d", T.intvalue);
}
break;
case T_FUNC: // 1015 func[0, 0]
scan(&T);
scan(&T);
if (T.token == T_INTLIT) {
numFunc = T.intvalue;
scan(&T);
scan(&T);
if (T.token == T_INTLIT) {
if (T.intvalue > 0)
bitSet(locoData[myLocoData].myFunc.Bits, numFunc);
else
bitClear(locoData[myLocoData].myFunc.Bits, numFunc);
DEBUG_MSG("Func%d: %d", numFunc, T.intvalue);
updateFuncState(isWindow(WIN_THROTTLE));
}
}
break;
/*
case T_FSYMBOL: // 1015 funcsymbol[0,2]
scan(&T);
scan(&T);
if (T.token == T_INTLIT) {
numFunc = T.intvalue;
scan(&T);
scan(&T);
if (T.token == T_INTLIT) {
locoData[myLocoData].myFuncIcon[numFunc] = FunktionsTastenSymbole[T.intvalue & 0x7F] << 1;
DEBUG_MSG("Func: %d, Icon: %d", numFunc, T.intvalue)
}
}
break;
*/
case T_FSYMB: // 1015 funcsymb[0,2]
scan(&T);
scan(&T);
if (T.token == T_INTLIT) {
numFunc = T.intvalue;
scan(&T);
scan(&T);
if (T.token == T_INTLIT) {
locoData[myLocoData].myFuncIcon[numFunc] = FunktionsTastenSymboleCS1[T.intvalue & 0x3F] << 1;
DEBUG_MSG("Func: %d, Icon: %d", numFunc, T.intvalue)
}
}
break;
case T_FICON: // 1000 funcicon[0,3,light]
scan(&T);
scan(&T);
if (T.token == T_INTLIT) {
numFunc = T.intvalue;
scan(&T);
scan(&T);
if (T.token == T_INTLIT) {
locoData[myLocoData].myFuncIcon[numFunc] = FunktionsTastenSymbole[T.intvalue & 0x7F] << 1;
DEBUG_MSG("Func: %d, Icon: %d", numFunc, T.intvalue)
}
}
break;
}
discardLine();
}

View File

@@ -0,0 +1,197 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
*/
////////////////////////////////////////////////////////////
// ***** ENCODER *****
////////////////////////////////////////////////////////////
IRAM_ATTR void encoderISR () { // Encoder interrupt
encoderNeedService = true;
}
void encoderService () { // Encoder interrupt service
encoderNeedService = false;
lastTimeEncoder = millis();
outA = digitalRead (ENCODER_A);
outB = digitalRead (ENCODER_B);
if (outA != copyOutA) { // evitamos rebotes
copyOutA = outA;
if (copyOutB == 0x80) {
copyOutB = outB;
}
else {
if ( outB != copyOutB) {
copyOutB = 0x80;
if (outA == outB) // comprueba sentido de giro
encoderValue = (encoderValue < encoderMax) ? ++encoderValue : encoderMax ; // CW, hasta maximo
else
encoderValue = (encoderValue > 0) ? --encoderValue : 0; // CCW, hasta 0
encoderChange = true;
}
}
}
}
void readButtons () {
byte inputButton;
timeButtons = millis(); // lee cada cierto tiempo
inputButton = digitalRead (ENCODER_SW); // comprueba cambio en boton del encoder
if (statusSwitch != inputButton) {
statusSwitch = inputButton;
if (statusSwitch == LOW)
switchOn = true;
}
}
void controlEncoder() { // encoder movement
encoderChange = false;
aliveAndKicking();
DEBUG_MSG("Encoder: %d", encoderValue);
switch (objStack[lastWinStack].objID) {
case WIN_SSID:
scrSSID = encoderValue;
scanWiFiFill();
drawObject(OBJ_TXT, TXT_SSID1);
drawObject(OBJ_TXT, TXT_SSID2);
drawObject(OBJ_TXT, TXT_SSID3);
drawObject(OBJ_TXT, TXT_SSID4);
drawObject(OBJ_TXT, TXT_SSID5);
drawObject(OBJ_TXT, TXT_SSID6);
break;
case WIN_THROTTLE:
case WIN_SPEEDO:
case WIN_STA_PLAY:
updateMySpeed();
break;
case WIN_CHG_FUNC:
fncData[FNC_CHG].idIcon = encoderValue * 2;
drawObject(OBJ_FNC, FNC_CHG);
break;
case WIN_SEL_LOCO:
populateLocoList();
drawObject(OBJ_TXT, TXT_SEL_ADDR1);
drawObject(OBJ_TXT, TXT_SEL_NAME1);
drawObject(OBJ_TXT, TXT_SEL_ADDR2);
drawObject(OBJ_TXT, TXT_SEL_NAME2);
drawObject(OBJ_TXT, TXT_SEL_ADDR3);
drawObject(OBJ_TXT, TXT_SEL_NAME3);
drawObject(OBJ_TXT, TXT_SEL_ADDR4);
drawObject(OBJ_TXT, TXT_SEL_NAME4);
drawObject(OBJ_TXT, TXT_SEL_ADDR5);
drawObject(OBJ_TXT, TXT_SEL_NAME5);
drawObject(OBJ_TXT, TXT_SEL_ADDR6);
drawObject(OBJ_TXT, TXT_SEL_NAME6);
break;
case WIN_STEAM:
showSpeedSteam((encoderValue << 1) + 240);
break;
case WIN_ACC_TYPE:
fncData[FNC_ACC_TYPE].num = accDef[encoderValue].num;
fncData[FNC_ACC_TYPE].idIcon = accDef[encoderValue].icon[0].fncIcon;
fncData[FNC_ACC_TYPE].color = accDef[encoderValue].icon[0].color;
fncData[FNC_ACC_TYPE].colorOn = accDef[encoderValue].icon[0].colorOn;
drawObject(OBJ_FNC, FNC_ACC_TYPE);
break;
}
}
void controlSwitch() { // encoder switch
uint16_t value, value2, txtID;
uint32_t delta, dist;
char msg[NAME_LNG + 1];
switchOn = false;
aliveAndKicking();
DEBUG_MSG("Encoder Switch");
switch (objStack[lastWinStack].objID) {
case WIN_SSID:
snprintf (wifiSetting.ssid, 32, WiFi.SSID(scrSSID).c_str()); //saveSSID(scrSSID);
DEBUG_MSG("New SSID: %s", wifiSetting.ssid);
eepromChanged = true;
closeWindow(WIN_SSID);
openWindow(WIN_WIFI);
break;
case WIN_THROTTLE:
case WIN_STA_PLAY:
if (encoderValue > 0) {
encoderValue = 0;
if (stopMode > 0)
locoData[myLocoData].mySpeed = 1;
else
locoData[myLocoData].mySpeed = 0;
locoOperationSpeed();
}
else {
locoData[myLocoData].myDir ^= 0x80;
changeDirection();
}
updateSpeedDir();
break;
case WIN_CHG_FUNC:
fncData[FNC_F0 + paramChild].idIcon = fncData[FNC_CHG].idIcon;
closeWindow(WIN_CHG_FUNC);
break;
case WIN_SEL_LOCO:
releaseLoco();
txtID = (encoderValue > 5) ? 5 : encoderValue;
if (useID) {
value2 = (encoderValue > 5) ? encoderValue - 5 : 0;
value = sortedLocoStack[value2 + txtID];
}
else {
value = atoi(txtData[TXT_SEL_ADDR1 + txtID].buf);
}
//value = atoi(txtData[TXT_SEL_ADDR1 + txtID].buf);
DEBUG_MSG("Selected Loco %d", value);
closeWindow(WIN_SEL_LOCO);
getNewLoco(value);
break;
case WIN_SPEEDO:
switch (speedoPhase) { //enum speedo {SPD_WAIT, SPD_BEGIN, SPD_COUNT, SPD_ARRIVE, SPD_END};
case SPD_WAIT:
if (getCurrentStep() > 0) {
speedoStartTime = millis();
setSpeedoPhase(SPD_BEGIN);
getLabelTxt(LBL_MEASURE, msg);
snprintf(spdSpeedBuf, NAME_LNG + 1, "%s", msg);
drawObject(OBJ_TXT, TXT_SPEEDO_SPD);
setTimer(TMR_SPEEDO, 5, TMR_ONESHOT);
}
else {
locoData[myLocoData].myDir ^= 0x80;
changeDirection();
updateSpeedDir();
}
break;
case SPD_BEGIN:
case SPD_COUNT:
speedoEndTime = millis();
setSpeedoPhase(SPD_ARRIVE);
setTimer(TMR_SPEEDO, 5, TMR_ONESHOT);
dist = speedoLength * 36 * speedoScale;
delta = (speedoEndTime - speedoStartTime) * 10;
speedoSpeed = dist / delta;
snprintf(spdSpeedBuf, NAME_LNG + 1, "%d km/h", speedoSpeed);
drawObject(OBJ_TXT, TXT_SPEEDO_SPD);
break;
case SPD_ARRIVE:
break;
case SPD_END:
break;
}
break;
case WIN_STEAM:
steamThrottleStop();
currentSteamSpeed = 0;
locoData[myLocoData].mySpeed = 0;
locoOperationSpeed();
break;
case WIN_ACC_TYPE:
accTypeClick();
break;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,417 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
*/
bool checkName(char *fileName) {
bool result;
uint16_t lng;
result = false;
lng = strlen(fileName);
if (lng > 4) {
if ((fileName[lng - 4] == '.') && (fileName[lng - 3] == 'c') && (fileName[lng - 2] == 's') && (fileName[lng - 1] == 'v'))
return true;
}
return result;
}
bool saveLocoData(fs::FS &fs, uint16_t pos) { // save loco data in .csv file
char field[30];
uint16_t cnt;
bool dataOK, isDir;
File myFile;
dataOK = false;
myFile = fs.open("/loco");
if (myFile) {
isDir = myFile.isDirectory();
myFile.close();
if (!isDir)
return dataOK;
DEBUG_MSG("/loco is a directory");
}
else {
DEBUG_MSG("Directory /loco not found. Creating...")
fs.mkdir("/loco");
}
sprintf (field, "/loco/%d.csv", locoData[pos].myAddr.address);
myFile = fs.open(field, FILE_WRITE);
if (myFile) {
DEBUG_MSG("File %s opened for writting", field);
getLabelTxt(LBL_NAME, field); // Header
myFile.print(field);
myFile.print(CSV_FILE_DELIMITER);
getLabelTxt(LBL_IMAGE, field);
myFile.print(field);
myFile.print(CSV_FILE_DELIMITER);
getLabelTxt(LBL_VMAX, field);
myFile.print(field);
for (cnt = 0; cnt < 29; cnt++) {
myFile.print(CSV_FILE_DELIMITER);
myFile.print('F');
myFile.print(cnt);
}
myFile.print("\r\n");
myFile.print(locoData[pos].myName); // Loco data
myFile.print(CSV_FILE_DELIMITER);
myFile.print(locoData[pos].myLocoID);
myFile.print(CSV_FILE_DELIMITER);
myFile.print(locoData[pos].myVmax);
for (cnt = 0; cnt < 29; cnt++) {
myFile.print(CSV_FILE_DELIMITER);
myFile.print(locoData[pos].myFuncIcon[cnt] >> 1);
}
myFile.print("\r\n");
myFile.close();
dataOK = true;
}
return dataOK;
}
void deleteLocoData (fs::FS &fs, uint16_t num)
{
char line[200];
sprintf (line, "/loco/%d.csv", num);
if (fs.remove(line)) {
DEBUG_MSG("File %s deleted", line);
}
else {
DEBUG_MSG("File %s delete failed", line);
}
}
bool readLocoData(fs::FS &fs, uint16_t num, uint16_t pos) { // read loco data from .csv file
char line[200];
bool dataOK;
uint16_t n;
File myFile;
dataOK = false;
sprintf (line, "/loco/%d.csv", num);
myFile = fs.open(line);
if (myFile) {
if (readCSV(myFile, line, sizeof(line), false)) { // skip header line
if (readCSV(myFile, locoData[pos].myName, sizeof(locoData[pos].myName), true)) { // read data field: Name
if (readCSV(myFile, line, sizeof(line), true)) { // read data field: Picture
locoData[pos].myLocoID = atoi(line);
if (readCSV(myFile, line, sizeof(line), true)) {
locoData[pos].myVmax = atoi(line);
DEBUG_MSG("%d %s %d %d", num, locoData[pos].myName, locoData[pos].myLocoID, locoData[pos].myVmax)
for (n = 0; n < 29; n++) {
if (!readCSV(myFile, line, sizeof(line), true)) { // read data field: Functions
myFile.close();
return false;
}
locoData[pos].myFuncIcon[n] = (uint8_t)(atoi(line) << 1);
//DEBUG_MSG("Func: %d - %d", n, locoData[pos].myFuncIcon[n])
}
locoData[pos].myAddr.address = num;
dataOK = true;
}
}
}
}
myFile.close();
}
return dataOK;
}
bool readCSV(File & f, char* line, uint16_t maxLen, bool getField) {
uint16_t n; // read field or line from CSV file
char chr;
n = 0;
yield();
while (n < maxLen) {
chr = f.read();
switch (chr) {
case 0:
line[n] = '\0';
return false; // EOF
break;
case '\r':
line[n] = '\0';
break;
case '\n':
line[n] = '\0';
return true;
break;
case ',':
case ';':
if (getField) {
line[n] = '\0';
return true;
}
break;
default:
line[n++] = chr;
break;
}
}
return false; // too long
}
void loadLocoFiles(fs::FS &fs, const char * dirname) {
uint16_t pos, adr;
char nameFile[50];
File myFile;
if (wifiSetting.protocol == CLIENT_ECOS)
return;
File root = fs.open(dirname);
if (!root) {
//DEBUG_MSG("Failed to open directory %s", dirname);
return;
}
if (!root.isDirectory()) {
//DEBUG_MSG("%s Not a directory", dirname);
return;
}
pos = 0;
File file = root.openNextFile();
while (file) {
if (! file.isDirectory()) {
if (pos < LOCOS_IN_STACK) {
sprintf(nameFile, "%s", file.name());
adr = atoi(nameFile);
//DEBUG_MSG("%d %s", adr, nameFile);
if (readLocoData(fs, adr, pos++))
pushLoco(adr);
}
}
file = root.openNextFile();
}
}
void initImageList() {
uint16_t n, maxImg;
uint16_t pos, id;
char nameFile[50];
File myFile;
for (n = 0; n < MAX_LOCO_IMAGE; n++) // add to list system images
locoImages[n] = (n < (MAX_SYS_LPIC - 1)) ? n + 1 : 0;
if (wifiSetting.protocol != CLIENT_ECOS) {
if (sdDetected) { // add to list user images from SD
File root = SD.open("/image");
if (root) {
if (root.isDirectory()) {
pos = MAX_SYS_LPIC - 1;
File file = root.openNextFile();
while (file) {
if (! file.isDirectory()) {
if (pos < MAX_LOCO_IMAGE) {
sprintf(nameFile, "%s", file.name());
id = atoi(nameFile);
if (id >= 1000) {
locoImages[pos++] = id;
}
}
}
file = root.openNextFile();
}
}
}
}
}
locoImageIndex = 0;
}
void populateImageList() {
uint16_t n;
for (n = 0; n < 6; n++)
lpicData[LPIC_SEL_IMG1 + n].id = locoImages[locoImageIndex + n];
}
bool saveCurrAccPanel(fs::FS &fs) {
char field[30];
uint16_t cnt;
bool dataOK, isDir;
File myFile;
dataOK = false;
myFile = fs.open("/acc");
if (myFile) {
isDir = myFile.isDirectory();
myFile.close();
if (!isDir)
return dataOK;
DEBUG_MSG("/acc is a directory");
}
else {
DEBUG_MSG("Directory /acc not found. Creating...")
fs.mkdir("/acc");
}
sprintf (field, "/acc/%d.csv", currPanel);
myFile = fs.open(field, FILE_WRITE);
if (myFile) {
DEBUG_MSG("File %s opened for writting", field);
getLabelTxt(LBL_ACC_TYPE, field); // Header
myFile.print(field);
myFile.print(CSV_FILE_DELIMITER);
getLabelTxt(LBL_ACC_ADDR, field);
myFile.print(field);
myFile.print(CSV_FILE_DELIMITER);
getLabelTxt(LBL_ACC_ADDR, field);
myFile.print(field);
myFile.print(CSV_FILE_DELIMITER);
getLabelTxt(LBL_ACC_NAME, field);
myFile.print(field);
myFile.print(CSV_FILE_DELIMITER);
getLabelTxt(LBL_BITS, field);
myFile.print(field);
myFile.print(CSV_FILE_DELIMITER);
myFile.print("\r\n");
for (cnt = 0; cnt < 16; cnt++) {
myFile.print(accPanel[cnt].type); // Acc data
myFile.print(CSV_FILE_DELIMITER);
myFile.print(accPanel[cnt].addr);
myFile.print(CSV_FILE_DELIMITER);
myFile.print(accPanel[cnt].addr2);
myFile.print(CSV_FILE_DELIMITER);
myFile.print(accPanel[cnt].accName);
myFile.print(CSV_FILE_DELIMITER);
myFile.print(accPanel[cnt].activeOutput);
myFile.print(CSV_FILE_DELIMITER);
myFile.print("\r\n");
}
myFile.close();
dataOK = true;
}
return dataOK;
}
bool loadAccPanel(fs::FS & fs) {
char line[200];
bool dataOK;
uint16_t cnt = 0;
File myFile;
dataOK = false;
sprintf (line, "/acc/%d.csv", currPanel);
myFile = fs.open(line);
if (myFile) {
if (readCSV(myFile, line, sizeof(line), false)) { // skip header line
for (cnt = 0; cnt < 16; cnt++) {
readCSV(myFile, line, sizeof(line), true);
accPanel[cnt].type = (accType)atoi(line);
readCSV(myFile, line, sizeof(line), true);
accPanel[cnt].addr = atoi(line);
readCSV(myFile, line, sizeof(line), true);
accPanel[cnt].addr2 = atoi(line);
readCSV(myFile, line, sizeof(line), true);
snprintf(accPanel[cnt].accName, ACC_LNG + 1, line);
readCSV(myFile, line, sizeof(line), false);
accPanel[cnt].activeOutput = atoi(line);
DEBUG_MSG("Line %d: %d, %d, %d, \"%s\", %d", cnt, accPanel[cnt].type, accPanel[cnt].addr, accPanel[cnt].addr2, accPanel[cnt].accName, accPanel[cnt].activeOutput)
}
}
myFile.close();
dataOK = true;
}
return dataOK;
}
bool saveAccPanelNames(fs::FS & fs) {
char field[30];
uint16_t cnt;
bool dataOK, isDir;
File myFile;
dataOK = false;
myFile = fs.open("/acc");
if (myFile) {
isDir = myFile.isDirectory();
myFile.close();
if (!isDir)
return dataOK;
DEBUG_MSG("/acc is a directory");
}
else {
DEBUG_MSG("Directory /acc not found. Creating...")
fs.mkdir("/acc");
}
sprintf (field, "/acc/panel.csv");
myFile = fs.open(field, FILE_WRITE);
if (myFile) {
DEBUG_MSG("File %s opened for writting", field);
getLabelTxt(LBL_NAME, field); // Header
myFile.print(field);
myFile.print("\r\n");
for (cnt = 0; cnt < 16; cnt++) {
myFile.print(panelNamesBuf[cnt]); // Panel names
myFile.print("\r\n");
}
myFile.close();
dataOK = true;
}
return dataOK;
}
bool loadAccPanelNames(fs::FS & fs) {
char line[200];
bool dataOK;
uint16_t n;
File myFile;
dataOK = false;
sprintf (line, "/acc/panel.csv");
myFile = fs.open(line);
if (myFile) {
if (readCSV(myFile, line, sizeof(line), false)) { // skip header line
for (n = 0; n < 16; n++) {
if (readCSV(myFile, line, sizeof(line), true)) { // read data field
snprintf(panelNamesBuf[n], PANEL_LNG + 1, line);
DEBUG_MSG("%s", panelNamesBuf[n])
}
}
}
myFile.close();
}
else {
for (n = 0; n < 16; n++) // default names
snprintf(panelNamesBuf[n], PANEL_LNG + 1, "Panel %d", n);
}
return dataOK;
}
/*
void listDir(fs::FS &fs, const char * dirname, uint8_t levels) {
Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if (!root) {
Serial.println("Failed to open directory");
return;
}
if (!root.isDirectory()) {
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while (file) {
if (file.isDirectory()) {
Serial.print(" DIR : ");
Serial.println(file.name());
if (levels) {
listDir(fs, file.path(), levels - 1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}
*/

View File

@@ -0,0 +1,151 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
Very basic Graphical User Interface (GUI) for PacoMouseCYD
All data in absolute coordinates
*/
#include <TFT_eSPI.h> // Graphics and font library for ILI9341 driver chip v2.5.43
#include "FreeSans7pt7b.h"
#include "FreeSansBold6pt7b.h"
#define MAX_OBJ_STACK 100
#define MAX_LABEL_LNG 150
#define TMR_RESOLUTION 100
#define NOT_USED 0xFF
#define OBJ_NOT_FOUND 0xFFFF
////////////////////////////////////////////////////////////
// ***** FONTS *****
////////////////////////////////////////////////////////////
#ifndef LOAD_GFXFF
ERROR_Please_enable_LOAD_GFXFF_in_User_Setup!
#endif
#define GFXFF 1
#define FSS7 &FreeSans7pt7b
#define FSS9 &FreeSans9pt7b
#define FSSB6 &FreeSansBold6pt7b
#define FSSB9 &FreeSansBold9pt7b
#define FSSB12 &FreeSansBold12pt7b
////////////////////////////////////////////////////////////
// ***** COLORS *****
////////////////////////////////////////////////////////////
// Colour definitions for 64K colour mode (RGB565)
// Bits 0..4 -> Blue 0..4
// Bits 5..10 -> Green 0..5
// Bits 11..15 -> Red 0..4
// Assign human-readable names to some common 16-bit color values: http://rinkydinkelectronics.com/calc_rgb565.php
// Examples: https://github.com/newdigate/rgb565_colors
#define COLOR_WHITE 0xFFFF
#define COLOR_BLACK 0x0000
#define COLOR_BLUE 0x001F
#define COLOR_NAVY 0x000F
#define COLOR_AQUA 0x5D1C
#define COLOR_SKYBLUE 0x867D
#define COLOR_RED 0xF882
#define COLOR_DARKRED 0x8800
#define COLOR_PINK 0xF97F
#define COLOR_MAGENTA 0xF81F
#define COLOR_GREEN 0x0780
#define COLOR_GREENYELLOW 0xAFE5
#define COLOR_DARKGREEN 0x03E0
#define COLOR_CYAN 0x07FF
#define COLOR_DARKCYAN 0x03EF
#define COLOR_YELLOW 0xFFE0
#define COLOR_GOLD 0xDD24
#define COLOR_LIGHTGREY 0xC618
#define COLOR_DARKGREY 0x7BEF
#define COLOR_LIGHTBLACK 0x4A49
#define COLOR_SMOKYBLACK 0x1061
#define COLOR_CHARCOAL 0x3A2A
#define COLOR_VIOLET 0x9199
#define COLOR_BROWN 0x8200
#define COLOR_ORANGE 0xFD20
#define COLOR_LIME 0x87E0
#define COLOR_MAROON 0x7800
#define COLOR_PURPLE 0x780F
#define COLOR_OLIVE 0x7BE0
#define COLOR_SILVER 0xA510
#define COLOR_CREAM 0xFFF9
#define COLOR_GHOST_WHITE 0xF7BF
#define COLOR_BACKGROUND 0xB5B6 // 0xB6B6B6
#define COLOR_TRANSPARENT TFT_TRANSPARENT
const uint16_t colorDraw[] = {COLOR_BLACK, COLOR_BLUE, COLOR_RED, COLOR_MAGENTA, COLOR_GREEN, COLOR_CYAN, COLOR_YELLOW, COLOR_WHITE,
COLOR_BACKGROUND, COLOR_TRANSPARENT, COLOR_GHOST_WHITE, COLOR_AQUA, COLOR_CREAM, COLOR_SMOKYBLACK, COLOR_SKYBLUE,
COLOR_GOLD,
};
////////////////////////////////////////////////////////////
// ***** OBJECT *****
////////////////////////////////////////////////////////////
enum objTypeGUI {OBJ_UNDEF, OBJ_TIMER, OBJ_WIN, OBJ_BUTTON, OBJ_LABEL, OBJ_TXT, OBJ_BAR, OBJ_DRAWSTR, OBJ_ICON, OBJ_KEYBOARD, OBJ_SWITCH,
OBJ_GAUGE, OBJ_LPIC, OBJ_FNC, OBJ_SLIDER, OBJ_RADIO, OBJ_CHAR,
};
struct wObj { // Graphic objects
uint16_t objType;
uint16_t objID;
};
struct wObj objStack[MAX_OBJ_STACK]; // Object stack
uint16_t endObjStack;
uint16_t lastWinStack;
////////////////////////////////////////////////////////////
// ***** EVENT *****
////////////////////////////////////////////////////////////
enum EventGUI {EVNT_CLICK, EVNT_DRAW, EVNT_WOPEN, EVNT_WCLOSE, EVNT_TIMER, EVNT_BOOT};
typedef struct { // Events
uint16_t objType;
uint16_t objID;
uint16_t eventID;
} wEvent;
wEvent eventStack[32]; // stack for events (size 32, hardcoded in functions. Don't change!)
uint16_t eventIn;
uint16_t eventOut;
uint16_t eventsPending;
uint16_t lastClickX;
uint16_t lastClickY;
////////////////////////////////////////////////////////////
// ***** TIMER *****
////////////////////////////////////////////////////////////
enum timers {TMR_BLIGHT, TMR_END_LOGO, TMR_POWER, TMR_SPEEDO, TMR_INFO, TMR_WAIT, TMR_STEAM, TMR_ACCESSORY, TMR_SCAN, TMR_FNC_ECOS,
TMR_STA_RUN,
MAX_SYS_TIMER
};
enum timerType {TMR_STOP, TMR_ONESHOT, TMR_PERIODIC};
typedef struct {
uint16_t tmrDelay;
uint16_t tmrCount;
uint16_t type;
} wTimer;
wTimer wTimerStack[MAX_SYS_TIMER];
uint32_t timerSys;
#if (TFT_WIDTH == 240)
#include "gui240x320.h"
#endif
#if (TFT_WIDTH == 320)
#include "gui320x480.h"
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,298 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
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.
*/
// OPCODES
#define OPC_GPOFF 0x82 // GLOBAL power OFF request
#define OPC_GPON 0x83 // GLOBAL power ON request
#define OPC_LOCO_SPD 0xA0 // SET SLOT speed
#define OPC_LOCO_DIRF 0xA1 // SET SLOT dir,F0-4 state
#define OPC_LOCO_SND 0xA2 // SET SLOT sound functions
#define OPC_LOCO_F9F12 0xA3 // Uhlenbrock
#define OPC_SW_REQ 0xB0 // REQ SWITCH function
#define OPC_SW_REP 0xB1 // Turnout SENSOR state REPORT
#define OPC_INPUT_REP 0xB2 // General SENSOR Input codes
#define OPC_LONG_ACK 0xB4 // Long acknowledge
#define OPC_SLOT_STAT1 0xB5 // WRITE slot stat1
#define OPC_MOVE_SLOTS 0xBA // MOVE slot SRC to DEST
#define OPC_RQ_SL_DATA 0xBB // Request SLOT DATA/status block
#define OPC_SW_STATE 0xBC // REQ state of SWITCH
#define OPC_LOCO_ADR_UHLI 0xBE // REQ loco ADR Uhlenbrock
#define OPC_LOCO_ADR 0xBF // REQ loco ADR
#define OPC_UHLI_FUN 0xD4 // Uhlenbrock
#define OPC_PEER_XFER 0xE5 // move 8 bytes PEER to PEER, SRC->DST
#define OPC_SL_RD_UHLI 0xE6 // SLOT DATA return, 21 bytes Uhlenbrock
#define OPC_SL_RD_DATA 0xE7 // SLOT DATA return, 10 bytes
#define OPC_IMM_PACKET 0xED // SEND n-byte packet immediate
#define OPC_WR_SL_UHLI 0xEE // WRITE SLOT DATA, 21 bytes Uhlenbrock
#define OPC_WR_SL_DATA 0xEF // WRITE SLOT DATA, 10 bytes
// BIT MASK
#define OPC_SW_REP_INPUTS 0x40 // sensor inputs, outputs otherwise
#define OPC_SW_REP_SW 0x20 // switch input, aux input otherwise
#define OPC_SW_REP_HI 0x10 // input is HI, LO otherwise
#define OPC_SW_REP_CLOSED 0x20 // 'Closed' line is ON, OFF otherwise
#define OPC_SW_REP_THROWN 0x10 // 'Thrown' line is ON, OFF otherwise
#define OPC_SW_REQ_DIR 0x20 // switch direction - closed/thrown
#define OPC_SW_REQ_OUT 0x10 // output On/Off
#define OPC_INPUT_REP_SW 0x20 // input is switch input, aux otherwise
#define OPC_INPUT_REP_HI 0x10 // input is HI, LO otherwise
#define STAT1_SL_BUSY 0x20 // BUSY/ACTIVE: bit encoding for SLOT activity
#define STAT1_SL_ACTIVE 0x10
#define GTRK_PROG_BUSY 0x08 // programming track is Busy
#define GTRK_IDLE 0x02 // 0 = Track paused, B'cast EMERG STOP, 1 = Power On
#define GTRK_POWER 0x01 // DCC packets are on and global power is up
// VALUES
#define SLOT_0 0x00 // Slot 0. Identifies command station type if implemented
#define SLOT_FC 0x7B // Fast clock slot
#define SLOT_PRG 0x7C // This slot communicates with the programming track
#define UHLI_PRG_START 0x41 // Intellibox II program task
#define UHLI_PRG_END 0x40
#define LNCV_REQID_CFGREAD 0x1F // LNCV task
#define LNCV_REQID_CFGWRITE 0x20
#define LNCV_REQID_CFGREQUEST 0x21
#define LNCV_FLAG_PRON 0x80
#define LNCV_FLAG_PROFF 0x40
// Message structure to determine the size of a message
typedef struct {
uint8_t command; /* LocoNet Op Code */
uint8_t mesg_size; /* size of the message in bytes */
} szMsg;
/* Turnout sensor state report */
typedef struct swrep_t {
uint8_t command;
uint8_t sn1; /* first byte of report */
uint8_t sn2; /* second byte of report */
uint8_t chksum; /* exclusive-or checksum for the message */
} swRepMsg;
/* Request Switch function */
typedef struct swreq_t {
uint8_t command;
uint8_t sw1; /* first byte of request */
uint8_t sw2; /* second byte of request */
uint8_t chksum; /* exclusive-or checksum for the message */
} swReqMsg;
/* Sensor input report */
typedef struct inputrep_t {
uint8_t command;
uint8_t in1; /* first byte of report */
uint8_t in2; /* second byte of report */
uint8_t chksum; /* exclusive-or checksum for the message */
} inputRepMsg;
/* Slot data request */
typedef struct slotreq_t {
uint8_t command;
uint8_t slot; /* slot number for this request */
uint8_t pad; /* should be zero */
uint8_t chksum; /* exclusive-or checksum for the message */
} slotReqMsg;
/* Read/Write Slot data messages */
typedef struct rwslotdata_t {
uint8_t command;
uint8_t mesg_size; /* ummmmm, size of the message in bytes? */
uint8_t slot; /* slot number for this request */
uint8_t stat; /* slot status */
uint8_t adr; /* loco address */
uint8_t spd; /* command speed */
uint8_t dirf; /* direction and F0-F4 bits */
uint8_t trk; /* track status */
uint8_t ss2; /* slot status 2 (tells how to use ID1/ID2 & ADV Consist*/
uint8_t adr2; /* loco address high */
uint8_t snd; /* Sound 1-4 / F5-F8 */
uint8_t id1; /* ls 7 bits of ID code */
uint8_t id2; /* ms 7 bits of ID code */
uint8_t chksum; /* exclusive-or checksum for the message */
} rwSlotDataMsg;
/* Fast Clock Message */
typedef struct fastclock_t {
uint8_t command;
uint8_t mesg_size; /* ummmmm, size of the message in bytes? */
uint8_t slot; /* slot number for this request */
uint8_t clk_rate; /* 0 = Freeze clock, 1 = normal, 10 = 10:1 etc. Max is 0x7f */
uint8_t frac_minsl; /* fractional minutes. not for external use. */
uint8_t frac_minsh;
uint8_t mins_60; /* 256 - minutes */
uint8_t track_stat; /* track status */
uint8_t hours_24; /* 256 - hours */
uint8_t days; /* clock rollovers */
uint8_t clk_cntrl; /* bit 6 = 1; data is valid clock info */
/* " " 0; ignore this reply */
uint8_t id1; /* id1/id2 is device id of last device to set the clock */
uint8_t id2; /* " " = zero shows not set has happened */
uint8_t chksum; /* exclusive-or checksum for the message */
} fastClockMsg;
/* Programmer Task Message (used in Start and Final Reply, both )*/
typedef struct progtask_t {
uint8_t command;
uint8_t mesg_size; /* ummmmm, size of the message in bytes? */
uint8_t slot; /* slot number for this request - slot 124 is programmer */
uint8_t pcmd; /* programmer command */
uint8_t pstat; /* programmer status error flags in reply message */
uint8_t hopsa; /* Ops mode - 7 high address bits of loco to program */
uint8_t lopsa; /* Ops mode - 7 low address bits of loco to program */
uint8_t trk; /* track status. Note: bit 3 shows if prog track is busy */
uint8_t cvh; /* hi 3 bits of CV# and msb of data7 */
uint8_t cvl; /* lo 7 bits of CV# */
uint8_t data7; /* 7 bits of data to program, msb is in cvh above */
uint8_t pad2;
uint8_t pad3;
uint8_t chksum; /* exclusive-or checksum for the message */
} progTaskMsg;
/* Set slot sound functions */
typedef struct locosnd_t {
uint8_t command;
uint8_t slot; /* slot number for this request */
uint8_t snd; /* sound/function request */
uint8_t chksum; /* exclusive-or checksum for the message */
} locoSndMsg;
/* Set slot direction and F0-F4 functions */
typedef struct locodirf_t {
uint8_t command;
uint8_t slot; /* slot number for this request */
uint8_t dirf; /* direction & function request */
uint8_t chksum; /* exclusive-or checksum for the message */
} locoDirfMsg;
/* Set slot speed functions */
typedef struct locospd_t {
uint8_t command;
uint8_t slot; /* slot number for this request */
uint8_t spd; /* speed request */
uint8_t chksum; /* exclusive-or checksum for the message */
} locoSpdMsg;
/* send packet immediate message */
typedef struct sendpkt_t {
uint8_t command;
uint8_t mesg_size; /* ummmmm, size of the message in bytes? */
uint8_t val7f; /* fixed value of 0x7f */
uint8_t reps; /* repeat count */
uint8_t dhi; /* high bits of data bytes */
uint8_t im1;
uint8_t im2;
uint8_t im3;
uint8_t im4;
uint8_t im5;
uint8_t chksum; /* exclusive-or checksum for the message */
} sendPktMsg;
/* Long ACK message */
typedef struct longack_t {
uint8_t command;
uint8_t opcode; /* op-code of message getting the response (msb=0) */
uint8_t ack1; /* response code */
uint8_t chksum; /* exclusive-or checksum for the message */
} longAckMsg;
/* Write slot status message */
typedef struct slotstat_t {
uint8_t command;
uint8_t slot; /* slot number for this request */
uint8_t stat; /* status to be written */
uint8_t chksum; /* exclusive-or checksum for the message */
} slotStatusMsg;
/* Move/Link Slot Message */
typedef struct slotmove_t {
uint8_t command;
uint8_t src; /* source slot number for the move/link */
uint8_t dest; /* destination slot for the move/link */
uint8_t chksum; /* exclusive-or checksum for the message */
} slotMoveMsg;
typedef struct
{
uint8_t command; // OPC_PEER_XFER for replies, OPC_IMM_PACKET for commands
uint8_t mesg_size; // 15 bytes
uint8_t SRC; // source
uint8_t DSTL; // destination, low byte
uint8_t DSTH; // destination, high byte
uint8_t ReqId; // Request ID, distinguishes commands
uint8_t PXCT1; // MSBs of following data
uint8_t D0; // Data Bytes
uint8_t D1;
uint8_t D2;
uint8_t D3;
uint8_t D4;
uint8_t D5;
uint8_t D6;
} UhlenbrockMsg;
typedef struct
{
uint8_t command; // OPC_SL_RD_UHLI for replies, OPC_WR_SL_UHLI for commands
uint8_t mesg_size; // 21 bytes
uint8_t unk0;
uint8_t slot; // slot number
uint8_t stat; // slot status
uint8_t adr; // loco address
uint8_t adr2; // loco address high
uint8_t trk; // track status
uint8_t spd; // command speed
uint8_t fhi; // function high bits: F12,F20,F28
uint8_t dirf; // direction and F0-F4 bits
uint8_t snd2; // F5..F11
uint8_t snd3; // F13..F19
uint8_t snd4; // F21..F27
uint8_t unk1; // steps???
uint8_t unk2;
uint8_t unk3;
uint8_t unk4;
uint8_t unk5;
uint8_t unk6;
} UhliSlotMsg;
typedef union {
szMsg sz ;
swRepMsg srp ;
swReqMsg srq ;
inputRepMsg ir ;
slotReqMsg sr ;
rwSlotDataMsg sd ;
fastClockMsg fc ;
progTaskMsg pt ;
locoSndMsg ls ;
locoDirfMsg ldf ;
locoSpdMsg lsp ;
sendPktMsg sp ;
longAckMsg lack ;
slotStatusMsg ss ;
slotMoveMsg sm ;
UhlenbrockMsg ub;
UhliSlotMsg usd;
uint8_t data[32] ;
} lnMsg ;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,104 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
*/
////////////////////////////////////////////////////////////
// ***** STATION RUN - MODEL TRAIN GAME FOR KIDS *****
////////////////////////////////////////////////////////////
void updateStationTime (uint16_t seconds) {
snprintf(staTimeBuf, ACC_LNG + 1, "%d:%02d", seconds / 60, seconds % 60);
}
void updateStationTarget() {
staStations = staLevel + 4;
if (staStartTime < 10)
staStartTime = 10;
staTime = staStartTime + ((staLevel - 1) * 10);
}
void newStationCounters (bool ini) {
if (ini) {
staStars = 0;
staLevel = 1;
randomSeed(millis());
}
staCurrStation = 0;
updateStationTarget();
}
uint8_t newStation(byte last) {
uint8_t station; // genera numero estacion sin repetir la ultima
do {
station = random (0, staMaxStations);
} while (station == last);
return (station);
}
void updateTargetStations() {
snprintf(staStationsBuf, ACC_LNG + 1, "%d", staStations);
}
void updateCountStations() {
snprintf(staStationsBuf, ACC_LNG + 1, "%d/%d", staCurrStation, staStations);
}
void updateStationLevel() {
snprintf(staLevelBuf, ADDR_LNG + 1, "%d", staLevel);
}
void updateStationStars() {
snprintf(staStarsBuf, ADDR_LNG + 1, "%d", staStars);
}
void setNewTarget() {
uint16_t pos;
staLastStation = newStation(staLastStation);
iconData[ICON_STA_TARGET].color = staColors[staLastStation];
pos = iconData[ICON_STA_TRAIN].x;
iconData[ICON_STA_TRAIN].x = iconData[ICON_STA_TARGET].x;
iconData[ICON_STA_TARGET].x = pos;
iconData[ICON_STA_PIN].x = pos + 8;
}
void clickTargetStation() {
encoderValue = 0;
locoData[myLocoData].mySpeed = 0;
locoOperationSpeed();
updateSpeedDir();
staCurrStation++;
if (staCurrStation == staStations) {
stopTimer(TMR_STA_RUN);
staLevel++;
updateStationLevel();
newStationCounters(false);
updateTargetStations();
updateStationTime(staTime);
closeWindow(WIN_STA_PLAY);
openWindow(WIN_STA_STARS); // well done!
}
else {
updateCountStations();
setNewTarget();
newEvent(OBJ_WIN, WIN_STA_PLAY, EVNT_DRAW);
}
}
uint16_t staGetTurnoutAdr(uint16_t eeAdr, uint16_t defAdr) {
uint16_t adr;
adr = (EEPROM.read(eeAdr) << 8) + EEPROM.read(eeAdr + 1);
if (adr > 2048)
adr = defAdr;
return adr;
}
void updateTurnoutButtons() {
uint16_t n, icon;
for (n = 0; n < 4; n++) {
if (staTurnoutPos[n])
fncData[FNC_STA_ACC0 + n].idIcon = bitRead(staTurnoutDef, n) ? FNC_TURNRD_OFF : FNC_TURNLD_OFF;
else
fncData[FNC_STA_ACC0 + n].idIcon = bitRead(staTurnoutDef, n) ? FNC_TURNRS_OFF : FNC_TURNLS_OFF;
fncData[FNC_STA_ACC0 + n].colorOn = staTurnoutPos[n] ? COLOR_RED : COLOR_GREEN;
}
}

View File

@@ -0,0 +1,399 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
*/
////////////////////////////////////////////////////////////
// ***** STEAM THROTTLE *****
////////////////////////////////////////////////////////////
void initSteamThrottle () {
uint16_t n;
if (oldSteamLoco != locoData[myLocoData].myAddr.address) {
oldSteamLoco = locoData[myLocoData].myAddr.address;
steamPressure = 80;
waterLevelBoiler = 80;
waterLevelTender = 350;
oldPressure = 0;
oldLevelBoiler = 0;
oldLevelTender = 0;
barData[BAR_JOHNSON].value = STEAM_JOHNSON_NEUTRAL; // neutral position
barData[BAR_BRAKE].value = 0;
steamDir = locoData[myLocoData].myDir;
for (n = 0; n < MAX_LIMIT; n++)
steamSpeedLimit[n] = LIMIT_NONE;
}
currentSteamTime = millis();
steamTimeSpeed = currentSteamTime;
steamTimeSteam = currentSteamTime;
steamTimeWater = currentSteamTime;
steamTimeLoad = currentSteamTime;
steamTimeSmoke = currentSteamTime;
shovelCoal = false;
setFirebox();
endWaterInjection();
endTenderFill();
setTimer(TMR_STEAM, 5, TMR_ONESHOT);
changeSmoke = STEAM_SMOKE_FAST;
oldPressure = 0;
oldSpeedSteam = 305;
encoderMax = 31; // set throttle to current speed
updateSteamThrottle();
if (steamDir != locoData[myLocoData].myDir) { // check Johnson bar position
steamDir = locoData[myLocoData].myDir;
barData[BAR_JOHNSON].value = 6 - barData[BAR_JOHNSON].value;
}
}
void updateSteamThrottle() {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
case CLIENT_XNET:
if (bitRead(locoData[myLocoData].mySteps, 2)) { // 0..127
currentSteamSpeed = locoData[myLocoData].mySpeed;
encoderValue = currentSteamSpeed >> 2;
}
else {
if (bitRead(locoData[myLocoData].mySteps, 1)) { // 0..31
encoderValue = (locoData[myLocoData].mySpeed & 0x0F) << 1;
if (bitRead(locoData[myLocoData].mySpeed, 4))
bitSet(encoderValue, 0);
currentSteamSpeed = (encoderValue > 3) ? (encoderValue << 2) + (encoderValue >> 3) : 0;
}
else { // 0..15
encoderValue = locoData[myLocoData].mySpeed & 0x0F;
encoderValue = (encoderValue > 1) ? encoderValue << 1 : 0;
currentSteamSpeed = (encoderValue << 2) + (encoderValue >> 3);
}
}
break;
case CLIENT_LNET:
case CLIENT_ECOS:
currentSteamSpeed = locoData[myLocoData].mySpeed;
encoderValue = currentSteamSpeed >> 2;
break;
}
}
void steamProcess() {
uint16_t value, newSpeed, mappedThrottle, jFactor;
steamSpeedLimit[LIMIT_THROTTLE] = (encoderValue << 2) + (encoderValue >> 3); // Read Throtle Speed (0..31 -> 0..127)
steamSpeedLimit[LIMIT_JOHNSON] = LIMIT_NONE;
switch (barData[BAR_JOHNSON].value) { // Read Johnson Bar
case 0: // full reverse
jFactor = 3;
changeSpeed = 80;
steamDir = 0x00;
break;
case 1:
jFactor = 2;
changeSpeed = 200;
steamDir = 0x00;
break;
case 2:
jFactor = 1;
changeSpeed = 500;
steamDir = 0x00;
break;
case STEAM_JOHNSON_NEUTRAL: // Neutral position of Johnson Bar
jFactor = 1;
changeSpeed = 1000;
steamSpeedLimit[LIMIT_JOHNSON] = 0;
break;
case 4:
jFactor = 1;
changeSpeed = 500;
steamDir = 0x80;
break;
case 5:
jFactor = 2;
changeSpeed = 200;
steamDir = 0x80;
break;
case 6: // full forward
jFactor = 3;
changeSpeed = 80;
steamDir = 0x80;
break;
}
if (steamDir != locoData[myLocoData].myDir) { // Check direction
locoData[myLocoData].myDir = steamDir;
changeDirection();
DEBUG_MSG("STEAM: Change direction")
}
value = steamSpeedLimit[LIMIT_THROTTLE];
changeSteam = 10000 - ((value * 9) * jFactor); // Steam timeout: 6571 to 10000
changeWater = 14000 - ((value * 27) * jFactor); // Water timeout: 3713 to 14000
if (barData[BAR_BRAKE].value > 0) { // Brake bar: 300, 150, 100
changeSpeed = 300 / barData[BAR_BRAKE].value;
}
currentSteamTime = millis();
if (currentSteamTime > (steamTimeWater + changeWater)) { // Water consumption
steamTimeWater = currentSteamTime;
if (waterLevelBoiler > 0) {
waterLevelBoiler--;
DEBUG_MSG("Boiler Level: %d", waterLevelBoiler)
}
}
if (waterLevelBoiler < 10) { // Stop loco if not enough water
steamSpeedLimit[LIMIT_WATER] = 0;
steamThrottleStop();
}
else {
steamSpeedLimit[LIMIT_WATER] = LIMIT_NONE;
}
if (currentSteamTime > (steamTimeSteam + changeSteam)) { // Steam consumption
steamTimeSteam = currentSteamTime;
if (steamPressure > 0)
steamPressure--;
}
if (steamPressure < 50) { // Limit speed based on steam level
value = (steamPressure < 20) ? 0 : map(steamPressure, 20, 50, 20, 120);
steamSpeedLimit[LIMIT_PRESSURE] = value;
}
else {
steamSpeedLimit[LIMIT_PRESSURE] = LIMIT_NONE;
}
if (currentSteamTime > (steamTimeLoad + STEAM_LOAD_TIME)) { // Load coal and water
steamTimeLoad = currentSteamTime;
if (shovelCoal) { // Fire open for shoveling coal
if (steamPressure > 96) {
shovelCoal = false;
setFirebox();
newEvent(OBJ_FNC, FNC_ST_FIRE, EVNT_DRAW);
}
else {
if (steamPressure < 20) // slowly pressure up at beginning
steamPressure += 1;
else
steamPressure += 2;
}
}
if (waterInjection) { // Water injector open
if (waterLevelTender > 0) { // Inject water with water from tender
if (waterLevelBoiler > 95) {
endWaterInjection();
}
else {
waterLevelBoiler += 2;
waterLevelTender--;
}
steamSpeedLimit[LIMIT_TENDER] = LIMIT_NONE;
}
else {
endWaterInjection(); // Stop locomotive if tender empty
steamSpeedLimit[LIMIT_TENDER] = 0;
steamThrottleStop();
}
}
if (fillTender) {
if ((waterLevelTender > 495) || (currentSteamSpeed != 0)) { // Only fill tender when stopped
endTenderFill();
}
else {
waterLevelTender++;
if (waterLevelTender > 6) // Minimum level to run again
steamSpeedLimit[LIMIT_TENDER] = LIMIT_NONE;
}
}
}
if (currentSteamTime > (steamTimeSmoke + changeSmoke)) { // Chimney smoke
steamTimeSmoke = currentSteamTime;
if (currentSteamSpeed > 0) {
fncData[FNC_ST_SMOKE].state = !fncData[FNC_ST_SMOKE].state;
changeSmoke = map(currentSteamSpeed, 0, 127, STEAM_SMOKE_SLOW, STEAM_SMOKE_FAST);
}
else {
fncData[FNC_ST_SMOKE].state = false;
changeSmoke = STEAM_SMOKE_SLOW + STEAM_SMOKE_SLOW;
}
newEvent(OBJ_FNC, FNC_ST_SMOKE, EVNT_DRAW);
}
if (barData[BAR_BRAKE].value > 0) { // Braking
value = barData[BAR_BRAKE].value * 8;
value = (currentSteamSpeed > value) ? (currentSteamSpeed - value) : 0;
steamSpeedLimit[LIMIT_BRAKE] = value;
}
else {
steamSpeedLimit[LIMIT_BRAKE] = LIMIT_NONE;
}
targetSpeedSteam = LIMIT_NONE; // Find lower limit
for (value = 0; value < MAX_LIMIT; value++) {
if (steamSpeedLimit[value] < targetSpeedSteam)
targetSpeedSteam = steamSpeedLimit[value];
}
newSpeed = currentSteamSpeed;
if (currentSteamTime > (steamTimeSpeed + changeSpeed)) { // Speed acceleration
steamTimeSpeed = currentSteamTime;
//DEBUG_MSG("Target: %d Current: %d New: %d", targetSpeedSteam, currentSteamSpeed, newSpeed)
if (targetSpeedSteam > currentSteamSpeed) {
newSpeed = currentSteamSpeed + 1;
DEBUG_MSG("Inc New: %d", newSpeed)
}
if (targetSpeedSteam < currentSteamSpeed) {
newSpeed = currentSteamSpeed - 1;
DEBUG_MSG("Dec New: %d", newSpeed)
}
//DEBUG_MSG("New acc: %d", newSpeed)
}
if (currentSteamSpeed != newSpeed) { // changes in speed
DEBUG_MSG("Step: %d - New: %d LIMITS ", currentSteamSpeed, newSpeed)
#ifdef DEBUG
for (value = 0; value < MAX_LIMIT; value++) {
Serial.print(steamSpeedLimit[value]);
Serial.print(" ");
}
Serial.println();
#endif
currentSteamSpeed = newSpeed;
switch (wifiSetting.protocol) {
case CLIENT_Z21:
case CLIENT_XNET:
if (bitRead(locoData[myLocoData].mySteps, 2)) { // 128 steps
mappedThrottle = (currentSteamSpeed > 1) ? currentSteamSpeed : 0;
}
else {
if (bitRead(locoData[myLocoData].mySteps, 1)) { // 28 steps
if (currentSteamSpeed > 15) {
mappedThrottle = currentSteamSpeed >> 3;
bitWrite(mappedThrottle, 4, bitRead(currentSteamSpeed, 2));
}
else {
mappedThrottle = 0;
}
}
else { // 14 steps
mappedThrottle = (currentSteamSpeed > 15) ? currentSteamSpeed >> 3 : 0;
}
}
break;
case CLIENT_LNET:
case CLIENT_ECOS:
mappedThrottle = (currentSteamSpeed > 1) ? currentSteamSpeed : 0;
break;
}
locoData[myLocoData].mySpeed = mappedThrottle;
locoOperationSpeed();
DEBUG_MSG("Steam step: %d", currentSteamSpeed)
if ((currentSteamSpeed > 0) && (changeSmoke > STEAM_SMOKE_SLOW)) { // initial chuff
changeSmoke = 0;
fncData[FNC_ST_SMOKE].state = false;
}
}
if ((oldLevelTender / 10) != (waterLevelTender / 10)) {
oldLevelTender = waterLevelTender;
barData[BAR_TENDER].value = waterLevelTender;
newEvent(OBJ_BAR, BAR_TENDER, EVNT_DRAW);
}
value = waterLevelBoiler / 2;
if (oldLevelBoiler != value ) {
oldLevelBoiler = value;
barData[BAR_WATER].value = value;
newEvent(OBJ_BAR, BAR_WATER, EVNT_DRAW);
}
value = steamPressure * 270 / 100;
if (oldPressure != value) {
showPressure(value);
DEBUG_MSG("Pressure: %d", steamPressure)
}
}
void startWaterInjection () {
waterInjection = true;
fncData[FNC_ST_WATER].colorOn = COLOR_DARKGREEN;
drawObject(OBJ_FNC, FNC_ST_WATER);
}
void endWaterInjection () {
waterInjection = false;
fncData[FNC_ST_WATER].colorOn = COLOR_RED;
drawObject(OBJ_FNC, FNC_ST_WATER);
}
void startTenderFill() {
fillTender = true;
//fncData[FNC_ST_TENDER].color = COLOR_RED;
fncData[FNC_ST_TENDER].colorOn = COLOR_DARKGREEN;
drawObject(OBJ_FNC, FNC_ST_TENDER);
}
void endTenderFill() {
fillTender = false;
//fncData[FNC_ST_TENDER].color = COLOR_WHITE;
fncData[FNC_ST_TENDER].colorOn = COLOR_RED;
drawObject(OBJ_FNC, FNC_ST_TENDER);
}
void setFirebox() {
if (shovelCoal) {
fncData[FNC_ST_FIRE].idIcon = FNC_FIRE_OP_OFF;
fncData[FNC_ST_FIRE].color = COLOR_ORANGE;
fncData[FNC_ST_FIRE].colorOn = COLOR_YELLOW;
}
else {
fncData[FNC_ST_FIRE].idIcon = FNC_FIRE_CL_OFF;
fncData[FNC_ST_FIRE].color = COLOR_SILVER;
fncData[FNC_ST_FIRE].colorOn = COLOR_RED;
}
}
void steamThrottleStop() { // set controls for stop
if (encoderValue > 0) {
encoderValue = 0;
showSpeedSteam(240);
}
if (barData[BAR_JOHNSON].value != STEAM_JOHNSON_NEUTRAL) {
barData[BAR_JOHNSON].value = STEAM_JOHNSON_NEUTRAL; // Neutral
drawObject(OBJ_BAR, BAR_JOHNSON);
}
}
void showPressure(uint16_t angle) {
tft.setPivot(140, 105); // Set pivot to center of manometer in TFT screen
sprite.setColorDepth(8); // Create an 8bpp Sprite
sprite.createSprite(19, 19); // 8bpp requires 19 * 19 = 361 bytes
sprite.setPivot(9, 9); // Set pivot relative to top left corner of Sprite
sprite.fillSprite(COLOR_WHITE); // Fill the Sprite with background
sprite.pushRotated(oldPressure);
oldPressure = angle;
sprite.drawBitmap(0, 0, needle_bar, 19, 19, COLOR_BLUE);
sprite.pushRotated(angle);
sprite.deleteSprite();
}
void showSpeedSteam(uint16_t angle) {
tft.setPivot(120, 170); // Set pivot to center of bar in TFT screen
sprite.setColorDepth(8); // Create an 8bpp Sprite
sprite.createSprite(83, 15); // 8bpp requires 83 * 15 = 1245 bytes
sprite.setPivot(76, 7); // Set pivot relative to top left corner of Sprite
sprite.fillSprite(COLOR_BLACK); // Fill the Sprite with background
sprite.pushRotated(oldSpeedSteam);
oldSpeedSteam = angle;
sprite.drawBitmap(0, 0, speed_steam, 83, 15, COLOR_RED);
sprite.pushRotated(angle);
tft.drawArc(120, 170, 27, 22, 315, 45, COLOR_RED, COLOR_BLACK, false);
sprite.deleteSprite();
}

View File

@@ -0,0 +1,842 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
*/
////////////////////////////////////////////////////////////
// ***** SYSTEM SUPPORT *****
////////////////////////////////////////////////////////////
void initPins() {
// Set all chip selects high to avoid bus contention during initialisation of each peripheral
digitalWrite(TFT_CS, HIGH); // TFT screen chip select
digitalWrite(SD_CS, HIGH); // SD card chips select
digitalWrite(XPT2046_CS, HIGH); // Touch screen chips select
pinMode (SW_BOOT, INPUT); // Button BOOT
pinMode (ENCODER_A, INPUT); // Encoder
pinMode (ENCODER_B, INPUT);
pinMode (ENCODER_SW, INPUT);
#if (USE_RGB_LED == PRESENT)
pinMode(RGB_LED_R, OUTPUT); // RGB LED
pinMode(RGB_LED_G, OUTPUT);
pinMode(RGB_LED_B, OUTPUT);
setColorRGB(0); // turn off RGB LED
#endif
}
void setBacklight (uint8_t value) { // set PWM backlight
#if (ESP_ARDUINO_VERSION_MAJOR > 2)
// Code for version 3.x
ledcWrite(TFT_BL, value);
#else
// Code for version 2.x
ledcWrite(LEDC_CHANNEL_0, value);
#endif
currBacklight = value;
}
void setRotationDisplay(uint8_t pos) { // Rotate display and touchscreen
tft.setRotation(pos);
touchscreen.setRotation((pos + XPT_ROTATION) & 0x03);
}
void aliveAndKicking() {
setTimer (TMR_BLIGHT, INACT_TIME, TMR_ONESHOT); // reset timeout and restore backlight
if (currBacklight != backlight)
setBacklight(backlight);
}
#if (USE_RGB_LED == PRESENT)
void setColorRGB (uint16_t color) { // set color of RGB LED
int state;
state = (color & 0x04) ? LOW : HIGH;
digitalWrite(RGB_LED_G, state);
state = (color & 0x02) ? LOW : HIGH;
digitalWrite(RGB_LED_R, state);
state = (color & 0x01) ? LOW : HIGH;
digitalWrite(RGB_LED_B, state);
DEBUG_MSG("Color: %d", color & 0x07)
}
#endif
initResult initSequence() { // Performs init sequence
char label[MAX_LABEL_LNG];
char chr;
int n;
initResult result;
result = INIT_OK;
delay(500);
drawObject(OBJ_ICON, ICON_SDCARD); // detecting SD card
if (sdDetected) {
sprintf (FileName, "/image/logo.bmp");
if (tft.width() == 240)
drawBmp (FileName, 0, 180);
else
drawBmp (FileName, 40, 260);
loadLocoFiles(SD, "/loco"); // load loco data & panel names from SD file
loadAccPanelNames(SD);
}
else {
if (LittleFS.begin(false)) {
loadLocoFiles(LittleFS, "/loco"); // load loco data & panel names from FS file
loadAccPanelNames(LittleFS);
}
else {
DEBUG_MSG("LittleFS Mount Failed. Formating....");
LittleFS.format();
}
drawObject(OBJ_ICON, ICON_NO_SD);
result = INIT_NO_SD;
DEBUG_MSG("Total: %ul Used: %ul", LittleFS.totalBytes(), LittleFS.usedBytes())
}
populateAccPanel(); // load first accessory panel
barData[BAR_INIT].value = 10;
drawObject(OBJ_BAR, BAR_INIT);
drawObject(OBJ_ICON, ICON_WIFI); // connecting to WiFi network
drawObject(OBJ_DRAWSTR, DSTR_INIT_STAT);
drawObject(OBJ_LABEL, LBL_CONNECT);
WiFi.mode(WIFI_STA);
WiFi.begin(wifiSetting.ssid, wifiSetting.password);
n = 0;
while ((WiFi.status() != WL_CONNECTED) && n < 80) { // tries to connect to router in 20 seconds
n += 2;
barData[BAR_INIT].value = 10 + n;
drawObject(OBJ_BAR, BAR_INIT);
delay(500);
DEBUG_MSG(".");
}
barData[BAR_INIT].value = 90;
drawObject(OBJ_BAR, BAR_INIT);
if (WiFi.status() == WL_CONNECTED) { // Connect to server with current protocol
drawObject(OBJ_DRAWSTR, DSTR_INIT_STAT); // show Protocol
getLabelTxt(LBL_SEL_Z21 + wifiSetting.protocol, label);
tft.drawString(label, 20, 120, GFXFF);
DEBUG_MSG("Channel: %d", WiFi.channel());
DEBUG_MSG("IP address: %u.%u.%u.%u", WiFi.localIP().operator[](0), WiFi.localIP().operator[](1), WiFi.localIP().operator[](2), WiFi.localIP().operator[](3));
DEBUG_MSG("%s", WiFi.macAddress().c_str())
useID = false;
switch (wifiSetting.protocol) {
case CLIENT_Z21:
WiFi.setSleep(false);
Udp.begin(z21Port);
//wifiSetting.port = z21Port;
DEBUG_MSG("Now listening UDP port %d", z21Port);
getStatusZ21(); // every x seconds
getSerialNumber();
delay(500);
setBroadcastFlags (0x00000013); // Broadcasts and info messages concerning driving and switching, report changes on feedback bus & fast clock
getStatusZ21();
//askZ21begin (LAN_GET_BROADCASTFLAGS);
//sendUDP (0x04);
break;
case CLIENT_LNET:
if (!Client.connect(wifiSetting.CS_IP, wifiSetting.port)) {
DEBUG_MSG("Connection to Loconet over TCP/IP failed");
result = INIT_NO_CONNECT;
}
else {
Client.setNoDelay(true);
rcvStrPhase = WAIT_TOKEN;
getTypeCS();
}
break;
case CLIENT_XNET:
if (!Client.connect(wifiSetting.CS_IP, XnetPort)) {
DEBUG_MSG("Connection to Xpressnet failed");
result = INIT_NO_CONNECT;
}
else {
wifiSetting.port = XnetPort;
Client.setNoDelay(true);
rxIndice = FRAME1;
getVersionXnet(); // pide la version del Xpressnet
getStatusXnet(); // pide estado de la central
}
break;
case CLIENT_ECOS:
useID = true;
if (!Client.connect(wifiSetting.CS_IP, ECoSPort)) {
DEBUG_MSG("Connection to ECoS failed");
result = INIT_NO_CONNECT;
}
else {
wifiSetting.port = ECoSPort;
Client.setNoDelay(true);
requestViews();
requestLocoList();
waitWifiData(500);
}
break;
}
}
else {
drawObject(OBJ_ICON, ICON_NO_WIFI);
result = INIT_NO_WIFI;
}
barData[BAR_INIT].value = 95;
drawObject(OBJ_BAR, BAR_INIT);
drawObject(OBJ_ICON, ICON_INIT_LOCO); // fill image list
initImageList();
barData[BAR_INIT].value = 100;
drawObject(OBJ_BAR, BAR_INIT);
setTimer (TMR_END_LOGO, 7, TMR_ONESHOT); // Wait for answer
return result;
}
bool notLocked () { // check if not locked
if (lockOptions & ((1 << LOCK_SEL_LOCO) | (1 << LOCK_TURNOUT) | (1 << LOCK_PROG)))
return false;
else
return true;
}
bool notLockedOption (byte opt) { // check if option not locked
if (lockOptions & (1 << opt))
return false;
else
return true;
}
////////////////////////////////////////////////////////////
// ***** TOUCHSCREEN *****
////////////////////////////////////////////////////////////
void calibrateTouchscreen(uint16_t colorIn, uint16_t colorOut, uint16_t bg) {
uint16_t TS_TOP, TS_BOT, TS_LEFT, TS_RT;
uint16_t x, y, z;
TSPoint p;
TS_TOP = 4095;
TS_BOT = 0;
TS_LEFT = 4095;
TS_RT = 0;
tft.fillScreen(bg);
for (int i = 0; i < 4; i++) {
tft.fillCircle(0, 0, 15, bg); // delete touch corners points
tft.fillCircle(tft.width(), 0, 15, bg);
tft.fillCircle(0, tft.height(), 15, bg);
tft.fillCircle(tft.width(), tft.height(), 15, bg);
DEBUG_MSG("Calibrate step: %d", i)
switch (i) { // show current touch corner point
case 0:
tft.fillCircle(0, 0, 15, colorOut);
tft.fillCircle(0, 0, 7, colorIn);
break;
case 1:
tft.fillCircle(tft.width(), 0, 15, colorOut);
tft.fillCircle(tft.width(), 0, 7, colorIn);
break;
case 2:
tft.fillCircle(0, tft.height(), 15, colorOut);
tft.fillCircle(0, tft.height(), 7, colorIn);
break;
case 3:
tft.fillCircle(tft.width(), tft.height(), 15, colorOut);
tft.fillCircle(tft.width(), tft.height(), 7, colorIn);
break;
}
while (touchscreen.touched()) // wait to release
delay(0);
DEBUG_MSG("Pen released")
while (!touchscreen.touched()) // wait to touch
delay(0);
DEBUG_MSG("Pen touched")
touchscreen.readData(&x, &y, &z);
if (x < TS_LEFT) {
TS_LEFT = x;
}
if (y < TS_TOP) {
TS_TOP = y;
}
if (x > TS_RT) {
TS_RT = x;
}
if (y > TS_BOT) {
TS_BOT = y;
}
}
tft.fillCircle(tft.width(), tft.height(), 15, bg); // delete last touch corner point
touchscreen.setCalibration(TS_LEFT, TS_RT, TS_TOP, TS_BOT);
DEBUG_MSG("xMin: %d, xMax: %d, yMin: %d, yMax: %d", TS_LEFT, TS_RT, TS_TOP, TS_BOT);
}
void showClockData(uint16_t txtFocus) {
uint16_t n;
for (n = 0; n < 3; n++)
txtData[TXT_HOUR + n].backgnd = COLOR_BACKGROUND;
txtData[txtFocus].backgnd = COLOR_YELLOW; // select focus on selected field
keybData[KEYB_CLOCK].idTextbox = txtFocus;
}
////////////////////////////////////////////////////////////
// ***** WIFI *****
////////////////////////////////////////////////////////////
void scanWiFi() {
networks = 0;
while (networks == 0) {
WiFi.disconnect(true); //DISCONNECT WITH TRUE (SHUOLD TURN OFF THE RADIO)
delay(1000);
WiFi.mode(WIFI_STA); //CALLING THE WIFI MODE AS STATION
WiFi.scanDelete();
networks = WiFi.scanNetworks();
DEBUG_MSG("Networks: %d", networks);
if ((networks > 0) && (networks < 32768)) {
encoderMax = networks - 1;
encoderValue = 0;
scrSSID = 0;
}
else
networks = 0;
}
}
void scanWiFiFill() {
uint16_t n, line;
n = (scrSSID > 5) ? scrSSID - 5 : 0;
snprintf (ssidName1, SSID_LNG, WiFi.SSID(n).c_str());
snprintf (ssidName2, SSID_LNG, WiFi.SSID(n + 1).c_str());
snprintf (ssidName3, SSID_LNG, WiFi.SSID(n + 2).c_str());
snprintf (ssidName4, SSID_LNG, WiFi.SSID(n + 3).c_str());
snprintf (ssidName5, SSID_LNG, WiFi.SSID(n + 4).c_str());
snprintf (ssidName6, SSID_LNG, WiFi.SSID(n + 5).c_str());
line = (scrSSID > 5) ? 5 : scrSSID;
for (n = 0; n < 6; n++) {
txtData[TXT_SSID1 + n].backgnd = (n == line) ? COLOR_BLUE : COLOR_BLACK;
}
}
void wifiAnalyzer() {
int16_t n, i;
char txt[10];
for (n = 0; n < 14; n++) {
ap_count[n] = 0;
max_rssi[n] = RSSI_FLOOR;
}
n = WiFi.scanNetworks();
drawObject(OBJ_DRAWSTR, DSTR_WIFI_SCAN);
drawObject(OBJ_LABEL, LBL_SSID_SCAN);
drawObject(OBJ_FNC, FNC_SCAN_RESET);
tft.setFreeFont(FSSB6);
if ((n > 0) && (n < 32768)) {
for (i = 0; i < n; i++) {
int32_t channel = WiFi.channel(i);
int32_t rssi = WiFi.RSSI(i);
uint16_t color = channel_color[channel - 1];
int height = constrain(map(rssi, RSSI_FLOOR, RSSI_CEILING, 1, GRAPH_HEIGHT), 1, GRAPH_HEIGHT);
// channel stat
ap_count[channel - 1]++;
if (rssi > max_rssi[channel - 1]) {
max_rssi[channel - 1] = rssi;
}
tft.drawLine((channel * CHANNEL_WIDTH) + GRAPH_OFFSET, GRAPH_BASELINE - height, ((channel - 1) * CHANNEL_WIDTH) + GRAPH_OFFSET, GRAPH_BASELINE + 1, color);
tft.drawLine((channel * CHANNEL_WIDTH) + GRAPH_OFFSET, GRAPH_BASELINE - height, ((channel + 1) * CHANNEL_WIDTH) + GRAPH_OFFSET, GRAPH_BASELINE + 1, color);
// Print SSID, signal strengh and if not encrypted
tft.setTextColor(color);
tft.setTextDatum(MC_DATUM);
tft.drawString(WiFi.SSID(i), (channel * CHANNEL_WIDTH) + GRAPH_OFFSET, GRAPH_BASELINE - 10 - height, GFXFF);
// rest for WiFi routine?
delay(10);
}
}
else {
tft.setFreeFont(FSSB9);
tft.setTextColor(COLOR_WHITE);
tft.drawString("SSID = 0", 120 + GRAPH_OFFSET, 120, GFXFF);
}
tft.setFreeFont(FSSB6);
tft.setTextDatum(TC_DATUM);
for (i = 1; i < 15; i++) {
tft.setTextColor(channel_color[i - 1]);
snprintf(txt, 10, "%d", i);
tft.drawString(txt, (i * CHANNEL_WIDTH) + GRAPH_OFFSET, GRAPH_BASELINE + 12, GFXFF);
if (ap_count[i - 1] > 0) {
snprintf(txt, 10, "(%d)", ap_count[i - 1]);
tft.drawString(txt, (i * CHANNEL_WIDTH) + GRAPH_OFFSET, GRAPH_BASELINE + 24, GFXFF);
}
}
setTimer(TMR_SCAN, 50, TMR_ONESHOT);
}
void wifiProcess() {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
processZ21();
break;
case CLIENT_XNET:
processXnet();
break;
case CLIENT_LNET:
processLnet();
break;
case CLIENT_ECOS:
ECoSProcess();
break;
}
}
void waitWifiData(uint32_t ms) {
uint32_t now;
now = millis();
while ((millis() - now) < ms)
wifiProcess();
}
void setProtocolData() {
uint16_t n;
useID = false;
switch (wifiSetting.protocol) {
case CLIENT_Z21:
snprintf(keybProtoBuf, PWD_LNG, "Z21");
snprintf(keybPortBuf, 6, "%d", z21Port);
break;
case CLIENT_LNET:
if (wifiSetting.serverType)
snprintf(keybProtoBuf, PWD_LNG, "LBServer");
else
snprintf(keybProtoBuf, PWD_LNG, "Loconet Binary");
snprintf(keybPortBuf, 6, "%d", wifiSetting.port);
break;
case CLIENT_XNET:
snprintf(keybProtoBuf, PWD_LNG, "Xpressnet LAN");
snprintf(keybPortBuf, 6, "%d", XnetPort);
break;
case CLIENT_ECOS:
useID = true;
snprintf(keybProtoBuf, PWD_LNG, "ECoS");
snprintf(keybPortBuf, 6, "%d", ECoSPort);
break;
}
for (n = 0; n < 5; n++)
txtData[TXT_IP1 + n].backgnd = COLOR_BACKGROUND;
txtData[TXT_IP1].backgnd = COLOR_YELLOW; // select focus on first IP byte
keybData[KEYB_IP].idTextbox = TXT_IP1;
}
void setOptionsData() {
switchData[SW_OPT_ADR].state = false; // show disable all as default
switchData[SW_OPT_ADR].colorKnob = COLOR_BACKGROUND;
radioData[RAD_CSTATION].value = radioData[RAD_CSTATION].num;
switch (wifiSetting.protocol) {
case CLIENT_Z21:
switchData[SW_OPT_ADR].colorKnob = COLOR_WHITE;
switchData[SW_OPT_ADR].state = (shortAddress == 99) ? true : false;
break;
case CLIENT_XNET:
break;
case CLIENT_LNET:
radioData[RAD_CSTATION].value = typeCmdStation;
break;
case CLIENT_ECOS:
break;
}
}
////////////////////////////////////////////////////////////
// ***** PROTOCOL COMMON *****
////////////////////////////////////////////////////////////
void infoLocomotora (unsigned int loco) {
DEBUG_MSG("Info Loco % d", loco)
switch (wifiSetting.protocol) {
case CLIENT_Z21:
infoLocomotoraZ21 (loco);
break;
case CLIENT_XNET:
infoLocomotoraXnet (loco);
break;
case CLIENT_LNET:
infoLocomotoraLnet (loco);
break;
case CLIENT_ECOS:
infoLocomotoraECoS (loco); // ID
break;
}
}
void locoOperationSpeed() {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
locoOperationSpeedZ21();
break;
case CLIENT_XNET:
locoOperationSpeedXnet();
break;
case CLIENT_LNET:
locoOperationSpeedLnet();
break;
case CLIENT_ECOS:
locoOperationSpeedECoS();
break;
}
}
void changeDirection() {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
locoOperationSpeedZ21();
break;
case CLIENT_XNET:
locoOperationSpeedXnet();
break;
case CLIENT_LNET:
changeDirectionF0F4Lnet();
break;
case CLIENT_ECOS:
changeDirectionECoS();
break;
}
}
void funcOperations (uint8_t fnc) {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
funcOperationsZ21 (fnc);
break;
case CLIENT_XNET:
funcOperationsXnet (fnc);
break;
case CLIENT_LNET:
funcOperationsLnet (fnc);
break;
case CLIENT_ECOS:
funcOperationsECoS(fnc);
break;
}
}
byte getCurrentStep() {
byte value;
switch (wifiSetting.protocol) {
case CLIENT_Z21:
value = getCurrentStepZ21();
break;
case CLIENT_XNET:
value = getCurrentStepXnet();
break;
case CLIENT_LNET:
value = getCurrentStepLnet();
break;
case CLIENT_ECOS:
value = getCurrentStepECoS();
break;
}
return value;
}
void releaseLoco() {
switch (wifiSetting.protocol) {
case CLIENT_LNET:
doDispatchGet = false;
doDispatchPut = false;
liberaSlot(); // pasa slot actual a COMMON
break;
case CLIENT_ECOS:
releaseLocoECoS();
break;
}
}
void sendAccessory(unsigned int FAdr, int pair, bool activate) {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
setAccessoryZ21(FAdr, pair, activate);
break;
case CLIENT_XNET:
setAccessoryXnet(FAdr, activate, pair);
break;
case CLIENT_LNET:
lnetRequestSwitch (FAdr, activate, pair);
break;
case CLIENT_ECOS:
setAccessoryECoS(FAdr, pair, activate);
break;
}
}
void resumeOperations() {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
resumeOperationsZ21();
break;
case CLIENT_XNET:
resumeOperationsXnet();
break;
case CLIENT_LNET:
resumeOperationsLnet();
break;
case CLIENT_ECOS:
resumeOperationsECoS();
break;
}
}
void emergencyOff() {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
emergencyOffZ21();
break;
case CLIENT_XNET:
emergencyOffXnet();
break;
case CLIENT_LNET:
emergencyOffLnet();
break;
case CLIENT_ECOS:
emergencyOffECoS();
break;
}
}
void togglePower() {
if (isTrackOff())
resumeOperations(); // Track Power On
else
emergencyOff(); // Track Power Off
}
bool isTrackOff() {
bool state;
switch (wifiSetting.protocol) {
case CLIENT_Z21:
state = (csStatus & csTrackVoltageOff) ? true : false;
break;
case CLIENT_XNET:
state = (csStatus & csEmergencyOff) ? true : false;
break;
case CLIENT_LNET:
state = (bitRead(mySlot.trk, 0)) ? false : true;
break;
case CLIENT_ECOS:
state = (csStatus > 0) ? false : true;
break;
}
return state;
}
void getStatusCS() {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
getStatusZ21();
break;
case CLIENT_XNET:
getStatusXnet();
break;
case CLIENT_LNET:
getTypeCS(); // workaround, not defined for Lnet
break;
case CLIENT_ECOS:
getStatusECoS();
break;
}
}
void setTime(byte hh, byte mm, byte rate) {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
setTimeZ21(hh, mm, rate);
break;
case CLIENT_XNET:
setTimeXnet(hh, mm, rate);
break;
case CLIENT_LNET:
setTimeLnet(hh, mm, rate);
break;
// ECoS not supported
}
}
void readCV (unsigned int adr, byte stepPrg) {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
readCVZ21(adr, stepPrg);
break;
case CLIENT_XNET:
readCVXnet(adr, stepPrg);
break;
case CLIENT_LNET:
readCVLnet(adr, stepPrg);
break;
case CLIENT_ECOS:
readCVECoS(adr, stepPrg);
break;
}
}
void writeCV (unsigned int adr, unsigned int data, byte stepPrg) {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
writeCVZ21(adr, data, stepPrg);
break;
case CLIENT_XNET:
writeCVXnet(adr, data, stepPrg);
break;
case CLIENT_LNET:
writeCVLnet(adr, data, stepPrg);
break;
case CLIENT_ECOS:
writeCVECoS(adr, data, stepPrg);
break;
}
}
void exitProgramming() {
switch (wifiSetting.protocol) {
case CLIENT_Z21:
if (csStatus & csProgrammingModeActive)
resumeOperationsZ21();
break;
case CLIENT_XNET:
if (csStatus & csServiceMode)
resumeOperationsXnet();
break;
case CLIENT_ECOS:
exitProgrammingECoS();
break;
}
}
////////////////////////////////////////////////////////////
// ***** CV PROGRAMMING *****
////////////////////////////////////////////////////////////
void endProg() { // Fin de programcion/lectura CV
DEBUG_MSG("END PROG: CVData - % d Step: % d", CVdata, progStepCV);
if (CVdata > 255) {
if (progStepCV == PRG_RD_CV29) // Si buscaba direccion, muestra CV1 en lugar de CV29
CVaddress = 1;
showDataCV();
}
else {
switch (progStepCV) {
case PRG_CV:
showDataCV();
break;
case PRG_RD_CV29:
cv29 = (byte) CVdata;
if (bitRead(cv29, 5)) {
CVaddress = 17; // Long address
readCV(CVaddress, PRG_RD_CV17);
}
else {
CVaddress = 1; // Short address
readCV(CVaddress, PRG_RD_CV1);
}
break;
case PRG_RD_CV1:
decoAddress = CVdata;
showDirCV();
break;
case PRG_RD_CV17:
cv17 = (byte) CVdata;
CVaddress = 18;
readCV(CVaddress, PRG_RD_CV18);
break;
case PRG_RD_CV18:
cv18 = (byte) CVdata;
decoAddress = ((cv17 & 0x3F) << 8) | cv18;
showDirCV();
break;
case PRG_WR_CV17: // Long address
CVaddress = 18;
writeCV(CVaddress, lowByte(decoAddress), PRG_WR_CV18);
break;
case PRG_WR_CV18:
bitSet(cv29, 5);
CVaddress = 29;
writeCV(CVaddress, cv29, PRG_WR_CV29);
break;
case PRG_WR_CV1: // short address
bitClear(cv29, 5);
CVaddress = 29;
writeCV(CVaddress, cv29, PRG_WR_CV29);
break;
case PRG_WR_CV29:
showDirCV();
break;
}
}
}
void showDataCV() { // muestra valor de la CV
progStepCV = PRG_IDLE;
enterCVdata = (CVdata > 255) ? false : true;
setStatusCV(); // show error / manufacturer / CV / pom
setFieldsCV();
setBitsCV();
if (isWindow(WIN_ALERT))
closeWindow(WIN_ALERT);
if (isWindow(WIN_PROG_CV)) {
showFieldsCV();
newEvent(OBJ_TXT, TXT_CV_STATUS, EVNT_DRAW);
}
if (wifiSetting.protocol == CLIENT_LNET)
progUhli(UHLI_PRG_END);
}
void showDirCV() { // muestra direccion de la locomotora segun sus CV
progStepCV = PRG_IDLE;
setStatusCV(); // show error / manufacturer / CV / pom
setFieldsCV();
setBitsCV();
if (isWindow(WIN_ALERT))
closeWindow(WIN_ALERT);
sprintf(locoEditAddr, " % d", decoAddress);
openWindow(WIN_PROG_ADDR);
if (wifiSetting.protocol == CLIENT_LNET)
progUhli(UHLI_PRG_END);
if (wifiSetting.protocol != CLIENT_ECOS)
pushLoco(decoAddress); // mete esta loco en el stack
}
void readBasicCV (uint16_t num) {
closeWindow(WIN_READ_CV);
if (num == 1) {
readCV(29, PRG_RD_CV29);
}
else {
CVaddress = num;
readCV(num, PRG_CV);
}
alertWindow(ERR_CV);
}
////////////////////////////////////////////////////////////
// ***** EEPROM *****
////////////////////////////////////////////////////////////
void saveCalibrationValues() {
TouchCalibration cal;
cal = touchscreen.getCalibration();
EEPROM.write (EE_XMIN_H, highByte(cal.xMin));
EEPROM.write (EE_XMIN_L, lowByte(cal.xMin));
EEPROM.write (EE_XMAX_H, highByte(cal.xMax));
EEPROM.write (EE_XMAX_L, lowByte(cal.xMax));
EEPROM.write (EE_YMIN_H, highByte(cal.yMin));
EEPROM.write (EE_YMIN_L, lowByte(cal.yMin));
EEPROM.write (EE_YMAX_H, highByte(cal.yMax));
EEPROM.write (EE_YMAX_L, lowByte(cal.yMax));
EEPROM.commit();
}

View File

@@ -0,0 +1,544 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
*/
enum language {LANG_ENGLISH, LANG_SPANISH, LANG_CATALAN, LANG_GERMAN, MAX_LANG};
static const char *translations[][MAX_LANG] = { // DON'T CHANGE ORDER: Same order defined in labelObj. If a complete translation is missing, must be English definition.
{ // LBL_PACO_TXT
[LANG_ENGLISH] = "PacoMouseCYD",
},
{ // LBL_INIT
[LANG_ENGLISH] = "Welcome!",
[LANG_SPANISH] = "Bienvenido!",
[LANG_CATALAN] = "Benvingut!",
[LANG_GERMAN] = "Willkommen!",
},
{ // LBL_CONNECT
[LANG_ENGLISH] = "Connecting...",
[LANG_SPANISH] = "Conectando...",
[LANG_CATALAN] = "Connectant...",
[LANG_GERMAN] = "Verbindung...",
},
{ // LBL_PRESS
[LANG_ENGLISH] = "Touch to start...",
[LANG_SPANISH] = "Toca para empezar...",
[LANG_CATALAN] = "Toca per iniciar...",
[LANG_GERMAN] = "Beruhren Sie den\nBildschirm, um \nzu beginnen...",
},
{ // LBL_CAL
[LANG_ENGLISH] = "To calibrate the\ntouchscreen, touch the\ncorners that appear\nwith the pen.",
[LANG_SPANISH] = "Para calibrar la pantalla,\ntoque las esquinas que\naparecen con el lapiz",
[LANG_CATALAN] = "Per calibrar la pantalla,\ntoqueu les cantonades que\napareixen amb el llapis",
[LANG_GERMAN] = "Um den Touchscreen\nzu kalibrieren, beruhren\nSie die angezeigten Ecken\nmit dem Stift.",
},
{ // LBL_CAL_DONE
[LANG_ENGLISH] = "Calibration done.",
[LANG_SPANISH] = "Calibracion realizada.",
[LANG_CATALAN] = "Calibratge realitzat.",
[LANG_GERMAN] = "Kalibrierung\nabgeschlossen.",
},
{ // LBL_SCAN
[LANG_ENGLISH] = "SSID WiFi\nScanning...",
[LANG_SPANISH] = "SSID WiFi\nBuscando......",
[LANG_CATALAN] = "SSID WiFi\nEscanejant...",
[LANG_GERMAN] = "SSID WiFi\nScannen...",
},
{ // LBL_SSID_SCAN
[LANG_ENGLISH] = "SSID WiFi",
},
{ // LBL_SSID
[LANG_ENGLISH] = "SSID",
},
{ // LBL_IP
[LANG_ENGLISH] = "IP",
},
{ // LBL_PWD_HIDE
[LANG_ENGLISH] = "Password",
[LANG_SPANISH] = NULL,
[LANG_CATALAN] = NULL,
[LANG_GERMAN] = "Passwort",
},
{ // LBL_PORT
[LANG_ENGLISH] = "Port",
[LANG_SPANISH] = "Puerto",
},
{ // LBL_PROTOCOL
[LANG_ENGLISH] = "Protocol",
[LANG_SPANISH] = "Protocolo",
[LANG_CATALAN] = NULL,
[LANG_GERMAN] = "Protokoll",
},
{ // LBL_SEL_PROT
[LANG_ENGLISH] = "Protocol",
[LANG_SPANISH] = "Protocolo",
[LANG_CATALAN] = NULL,
[LANG_GERMAN] = "Protokoll",
},
{ // LBL_SEL_Z21
[LANG_ENGLISH] = "Z21",
},
{ // LBL_SEL_XNET
[LANG_ENGLISH] = "Xpressnet LAN",
},
{ // LBL_SEL_ECOS
[LANG_ENGLISH] = "ECoS",
},
{ // LBL_SEL_LNET
[LANG_ENGLISH] = "Loconet over TCP/IP",
},
{ // LBL_SEL_LBSERVER
[LANG_ENGLISH] = "LBServer",
},
{ // LBL_SEL_BINARY
[LANG_ENGLISH] = "Binary",
},
{ // LBL_OPTIONS
[LANG_ENGLISH] = "Options",
[LANG_SPANISH] = "Opciones",
[LANG_CATALAN] = "Opcions",
[LANG_GERMAN] = "Optionen",
},
{ // LBL_NAME
[LANG_ENGLISH] = "Name",
[LANG_SPANISH] = "Nombre",
[LANG_CATALAN] = "Nom",
[LANG_GERMAN] = NULL,
},
{ // LBL_ADDR
[LANG_ENGLISH] = "Address",
[LANG_SPANISH] = "Direccion",
[LANG_CATALAN] = "Direccio",
[LANG_GERMAN] = "Adresse",
},
{ // LBL_IMAGE
[LANG_ENGLISH] = "Image",
[LANG_SPANISH] = "Imagen",
[LANG_CATALAN] = "Imatge",
[LANG_GERMAN] = "Bild",
},
{ // LBL_VMAX
[LANG_ENGLISH] = "Speed max.",
[LANG_SPANISH] = "Vel. max.",
[LANG_CATALAN] = "Vel. max.",
[LANG_GERMAN] = "Geschw.max.",
},
{ // LBL_FUNC
[LANG_ENGLISH] = "Functions",
[LANG_SPANISH] = "Funciones",
[LANG_CATALAN] = "Funcions",
[LANG_GERMAN] = "Funktionen",
},
{ // LBL_SERVICE
[LANG_ENGLISH] = "Service Mode",
[LANG_SPANISH] = "Modo Servicio",
[LANG_CATALAN] = "Mode Servei",
[LANG_GERMAN] = "Servicemodus",
},
{ // LBL_KMH
[LANG_ENGLISH] = "km/h",
},
{ // LBL_SHUNTING
[LANG_ENGLISH] = "Shunting",
[LANG_SPANISH] = "Maniobras",
[LANG_CATALAN] = "Maniobres",
[LANG_GERMAN] = "Rangieren",
},
{ // LBL_RATE
[LANG_ENGLISH] = "Rate 1:",
[LANG_SPANISH] = "Ratio 1:",
[LANG_CATALAN] = "Ratio 1:",
[LANG_GERMAN] = NULL,
},
{ // LBL_CHG_WIFI
[LANG_ENGLISH] = "Reset to apply\nchanges",
[LANG_SPANISH] = "Reinicie para\naplicar los\ncambios",
[LANG_CATALAN] = "Reiniciar per \naplicar els\ncanvis",
[LANG_GERMAN] = "Neustart, um die\nAnderungen zu\nübernehmen",
},
{ // LBL_EDIT_FUNC
[LANG_ENGLISH] = "Functions",
[LANG_SPANISH] = "Funciones",
[LANG_CATALAN] = "Funcions",
[LANG_GERMAN] = "Funktionen",
},
{ // LBL_STACK_FULL
[LANG_ENGLISH] = "Locomotive stack\nfull!",
[LANG_SPANISH] = "Almacen de\nlocomotoras\nlleno!",
[LANG_CATALAN] = "Magatzem de\nlocomotores\nple!",
[LANG_GERMAN] = "Lokomotivstapel\nvoll!",
},
{ // LBL_STOP_0
[LANG_ENGLISH] = "Speed 0",
[LANG_SPANISH] = "Velocidad 0",
[LANG_CATALAN] = "Velocitat 0",
[LANG_GERMAN] = "Geschw. 0",
},
{ // LBL_STOP_E
[LANG_ENGLISH] = "Emerg. Stop",
[LANG_SPANISH] = "Stop Emerg.",
[LANG_CATALAN] = "Stop Emerg.",
[LANG_GERMAN] = "Not-Halt",
},
{ // LBL_SEL_IMAGE
[LANG_ENGLISH] = "Image",
[LANG_SPANISH] = "Imagen",
[LANG_CATALAN] = "Imatge",
[LANG_GERMAN] = "Bild",
},
{ // LBL_MENU_DRIVE
[LANG_ENGLISH] = "Drive",
[LANG_SPANISH] = "Conducir",
[LANG_CATALAN] = "Conduir",
[LANG_GERMAN] = "Fahren",
},
{ // LBL_MENU_ACC
[LANG_ENGLISH] = "Accesory",
[LANG_SPANISH] = "Accesorios",
[LANG_CATALAN] = "Accesoris",
[LANG_GERMAN] = "Zubehorartikel",
},
{ // LBL_MENU_CV
[LANG_ENGLISH] = "CV Programming",
[LANG_SPANISH] = "Programar CV",
[LANG_CATALAN] = "Programar CV",
[LANG_GERMAN] = "CV-Programmierung",
},
{ // LBL_MENU_CFG
[LANG_ENGLISH] = "Configure",
[LANG_SPANISH] = "Configurar",
[LANG_CATALAN] = "Configurar",
[LANG_GERMAN] = "Einstellungen",
},
{ // LBL_MENU_UTILS
[LANG_ENGLISH] = "Utilities",
[LANG_SPANISH] = "Utilidades",
[LANG_CATALAN] = "Utilitats",
[LANG_GERMAN] = "Dienstprogramme",
},
{ // LBL_CFG_LANG
[LANG_ENGLISH] = "Language",
[LANG_SPANISH] = "Idioma",
[LANG_CATALAN] = "Idioma",
[LANG_GERMAN] = "Sprache",
},
{ // LBL_CFG_SCR
[LANG_ENGLISH] = "Screen",
[LANG_SPANISH] = "Pantalla",
[LANG_CATALAN] = "Pantalla",
[LANG_GERMAN] = "Bildschirm",
},
{ // LBL_CFG_SPD
[LANG_ENGLISH] = "Speed",
[LANG_SPANISH] = "Velocidad",
[LANG_CATALAN] = "Velocitat",
[LANG_GERMAN] = "Geschwindigkeit",
},
{ // LBL_CFG_WIFI
[LANG_ENGLISH] = "WiFi",
},
{ // LBL_CFG_FCLK
[LANG_ENGLISH] = "Fast Clock",
[LANG_SPANISH] = "Reloj",
[LANG_CATALAN] = "Rellotge",
[LANG_GERMAN] = "Uhr",
},
{ // LBL_CFG_LOCK
[LANG_ENGLISH] = "Lock",
[LANG_SPANISH] = "Bloquear",
[LANG_CATALAN] = "Bloquejar",
[LANG_GERMAN] = "Sperre",
},
{ // LBL_CFG_ABOUT
[LANG_ENGLISH] = "About...",
[LANG_SPANISH] = "Acerca...",
[LANG_CATALAN] = "Sobre...",
[LANG_GERMAN] = "Info...",
},
{ // LBL_SCR_ROTATE
[LANG_ENGLISH] = "Rotate",
[LANG_SPANISH] = "Girar",
[LANG_CATALAN] = "Girar",
[LANG_GERMAN] = "Drehen",
},
{ // LBL_PACO_WEB
[LANG_ENGLISH] = "https://usuaris.tinet.cat/fmco",
},
{ // LBL_LOCK_LOK
[LANG_ENGLISH] = "Locomotives",
[LANG_SPANISH] = "Locomotoras",
[LANG_CATALAN] = "Locomotores",
[LANG_GERMAN] = "Lokomotiven",
},
{ // LBL_LOCK_ACC
[LANG_ENGLISH] = "Accesory",
[LANG_SPANISH] = "Accesorios",
[LANG_CATALAN] = "Accesoris",
[LANG_GERMAN] = "Zubehorartikel",
},
{ // LBL_LOCK_PRG
[LANG_ENGLISH] = "Programming",
[LANG_SPANISH] = "Programar",
[LANG_CATALAN] = "Programar",
[LANG_GERMAN] = "Programmierung",
},
/*
{ // LBL_OPT_ROCO
[LANG_ENGLISH] = "Turntable offset",
[LANG_SPANISH] = "Offset Plataforma",
[LANG_CATALAN] = "Offset Plataforma",
[LANG_GERMAN] = "Drehscheibe Versatz",
},
*/
{ // LBL_OPT_ADR
[LANG_ENGLISH] = "Short Addr. (1 to 99)",
[LANG_SPANISH] = "Dir. corta (1 a 99)",
[LANG_CATALAN] = "Dir. curta (1 a 99)",
[LANG_GERMAN] = "Kurze Adr. (1 bis 99)",
},
{ // LBL_OPT_IB2
[LANG_ENGLISH] = "IBII / DR5000",
},
{ // LBL_OPT_UHLI
[LANG_ENGLISH] = "Uhlenbrock",
},
{ // LBL_OPT_DIG
[LANG_ENGLISH] = "Digitrax",
},
{ // LBL_ESTOP
[LANG_ENGLISH] = "Emergency Stop",
[LANG_SPANISH] = "Stop Emergencia",
[LANG_CATALAN] = "Stop Emergencia",
[LANG_GERMAN] = "Nothalt",
},
{ // LBL_SCALE
[LANG_ENGLISH] = "Scale",
[LANG_SPANISH] = "Escala",
[LANG_CATALAN] = "Escala",
[LANG_GERMAN] = "Skala",
},
{ // LBL_MM
[LANG_ENGLISH] = "mm",
},
{ // LBL_SCALE_H0
[LANG_ENGLISH] = "H0",
},
{ // LBL_SCALE_N
[LANG_ENGLISH] = "N",
},
{ // LBL_SCALE_TT
[LANG_ENGLISH] = "TT",
},
{ // LBL_SCALE_Z
[LANG_ENGLISH] = "Z",
},
{ // LBL_SCALE_0
[LANG_ENGLISH] = "0",
},
{ // LBL_MEASURE
[LANG_ENGLISH] = "Measuring",
[LANG_SPANISH] = "Midiendo",
[LANG_CATALAN] = "Mesurant",
[LANG_GERMAN] = "Messung",
},
{ // LBL_CV_ADDR
[LANG_ENGLISH] = "Loco Address",
[LANG_SPANISH] = "Direcc. Loco",
[LANG_CATALAN] = "Direcc. Loco",
[LANG_GERMAN] = "Lokadresse",
},
{ // LBL_CV_SPD_L
[LANG_ENGLISH] = "Speed min.",
[LANG_SPANISH] = "Velocidad min.",
[LANG_CATALAN] = "Velocitat min.",
[LANG_GERMAN] = "Minimale Geschw.",
},
{ // LBL_CV_SPD_M
[LANG_ENGLISH] = "Speed mid.",
[LANG_SPANISH] = "Velocidad media",
[LANG_CATALAN] = "Velocitat mitja",
[LANG_GERMAN] = "Mittlere Geschw.",
},
{ // LBL_CV_SPD_H
[LANG_ENGLISH] = "Speed max.",
[LANG_SPANISH] = "Velocidad max.",
[LANG_CATALAN] = "Velocitat max.",
[LANG_GERMAN] = "Maximale Geschw.",
},
{ // LBL_CV_ACC
[LANG_ENGLISH] = "Acceleration",
[LANG_SPANISH] = "Aceleracion",
[LANG_CATALAN] = "Acceleracio",
[LANG_GERMAN] = "Beschleunig",
},
{ // LBL_CV_DEC
[LANG_ENGLISH] = "Braking",
[LANG_SPANISH] = "Frenado",
[LANG_CATALAN] = "Frenada",
[LANG_GERMAN] = "Bremsen",
},
{ // LBL_CV_CFG
[LANG_ENGLISH] = "Configuration",
[LANG_SPANISH] = "Configuracion",
[LANG_CATALAN] = "Configuracio",
[LANG_GERMAN] = "Konfiguration",
},
{ // LBL_CV_MAN
[LANG_ENGLISH] = "Manufacturer",
[LANG_SPANISH] = "Fabricante",
[LANG_CATALAN] = "Fabricant",
[LANG_GERMAN] = "Hersteller",
},
{ // LBL_CV
[LANG_ENGLISH] = "CV",
},
{ // LBL_LNCV
[LANG_ENGLISH] = "LNCV",
},
{ // LBL_POM
[LANG_ENGLISH] = "PoM",
},
{ // LBL_BITS
[LANG_ENGLISH] = "Bits",
},
{ // LBL_CV_ERROR
[LANG_ENGLISH] = "CV ERROR",
[LANG_SPANISH] = "ERROR CV",
[LANG_CATALAN] = "ERROR CV",
[LANG_GERMAN] = "CV-FEHLER",
},
{ // LBL_UTIL_SPEED
[LANG_ENGLISH] = "Measure speed",
[LANG_SPANISH] = "Medir velocidad",
[LANG_CATALAN] = "Mesurar velocitat",
[LANG_GERMAN] = "Geschw. messen",
},
{ // LBL_UTIL_STEAM
[LANG_ENGLISH] = "Steam locomotive",
[LANG_SPANISH] = "Locomotora de vapor",
[LANG_CATALAN] = "Locomotora de vapor",
[LANG_GERMAN] = "Dampflokomotive",
},
{ // LBL_UTIL_SCAN
[LANG_ENGLISH] = "WiFi Analyzer",
[LANG_SPANISH] = "Analizador de WiFi",
[LANG_CATALAN] = "Analitzador de WiFi",
[LANG_GERMAN] = "WLAN-Analysator",
},
{ // LBL_UTIL_STA
[LANG_ENGLISH] = "Station Run",
[LANG_SPANISH] = "Carrera de estaciones",
[LANG_CATALAN] = "Carrera d'estacions",
[LANG_GERMAN] = "Bahnhofsrennen",
},
{ // LBL_ASK_SURE
[LANG_ENGLISH] = "Are you sure?",
[LANG_SPANISH] = "Estas seguro?",
[LANG_CATALAN] = "Segur?",
[LANG_GERMAN] = "Bist du sicher?",
},
{ // LBL_OPT_DISCOVER
[LANG_ENGLISH] = "Discover",
[LANG_SPANISH] = "Descubrir",
[LANG_CATALAN] = "Descobrir",
[LANG_GERMAN] = "Entdecken",
},
{ // LBL_LNCV_ART
[LANG_ENGLISH] = "Article",
[LANG_SPANISH] = "Articulo",
[LANG_CATALAN] = NULL,
[LANG_GERMAN] = "Artikel",
},
{ // LBL_LNCV_MOD
[LANG_ENGLISH] = "Module",
[LANG_SPANISH] = "Modulo",
[LANG_CATALAN] = "Modul",
[LANG_GERMAN] = "Modul",
},
{ // LBL_LNCV_NUM
[LANG_ENGLISH] = "LNCV",
},
{ // LBL_ACC_TYPE
[LANG_ENGLISH] = "Accessory type",
[LANG_SPANISH] = "Tipo accesorio",
[LANG_CATALAN] = "Tipus accessori",
[LANG_GERMAN] = "Zubehortyp",
},
{ // LBL_ACC_NAME
[LANG_ENGLISH] = "Name",
[LANG_SPANISH] = "Nombre",
[LANG_CATALAN] = "Nom",
[LANG_GERMAN] = NULL,
},
{ // LBL_ACC_ADDR
[LANG_ENGLISH] = "Addr.",
[LANG_SPANISH] = "Dir.",
[LANG_CATALAN] = "Dir.",
[LANG_GERMAN] = "Adr.",
},
{ // LBL_STA_RUN
[LANG_ENGLISH] = "Station Run",
[LANG_SPANISH] = "Carrera de estaciones",
[LANG_CATALAN] = "Carrera d'estacions",
[LANG_GERMAN] = "Bahnhofsrennen",
},
{ // LBL_STA_LEVEL
[LANG_ENGLISH] = "Level:",
[LANG_SPANISH] = "Nivel:",
[LANG_CATALAN] = "Nivell:",
[LANG_GERMAN] = NULL,
},
{ // LBL_STA_START
[LANG_ENGLISH] = "Start",
[LANG_SPANISH] = "Iniciar",
[LANG_CATALAN] = "Iniciar",
[LANG_GERMAN] = "Starten",
},
{ // LBL_STA_INSTR
[LANG_ENGLISH] = "Go to the station of your\ndestination color.\nPress it when you arrive",
[LANG_SPANISH] = "Ve a la estacion del color\nde tu destino.\nPulsala cuando llegues",
[LANG_CATALAN] = "Ves a l'estacio del color\nde la teva destinacio.\nPrem-la quan arribis",
[LANG_GERMAN] = "Gehen Sie sich zum Bahnhof\nIhrer Zielfarbe. Klicken Sie\ndas Symbol, wenn Sie ankommen",
},
{ // LBL_STA_EXCEL
[LANG_ENGLISH] = "Excellent!",
[LANG_SPANISH] = "Excelente!",
[LANG_CATALAN] = "Excel.lent!",
[LANG_GERMAN] = "Exzellent!",
},
{ // LBL_STA_GREAT
[LANG_ENGLISH] = "Great!",
[LANG_SPANISH] = "Muy bien!",
[LANG_CATALAN] = "Molt be!",
[LANG_GERMAN] = "Sehr gut!",
},
{ // LBL_STA_TIMEOUT
[LANG_ENGLISH] = "TIME'S UP\nThanks for playing",
[LANG_SPANISH] = "FIN DEL TIEMPO\nGracias por jugar",
[LANG_CATALAN] = "FINAL DEL TEMPS\nGracies per jugar",
[LANG_GERMAN] = "DIE ZEIT IST UM\nDanke furs Spielen",
},
{ // LBL_STA_STATIONS
[LANG_ENGLISH] = "Stations",
[LANG_SPANISH] = "Estaciones",
[LANG_CATALAN] = "Estacions",
[LANG_GERMAN] = "Bahnhofe",
},
{ // LBL_STA_TURNOUTS
[LANG_ENGLISH] = "Turnouts",
[LANG_SPANISH] = "Desvios",
[LANG_CATALAN] = "Agulles",
[LANG_GERMAN] = "Weichen",
},
{ // LBL_STA_TIME
[LANG_ENGLISH] = "Time",
[LANG_SPANISH] = "Tiempo",
[LANG_CATALAN] = "Temps",
[LANG_GERMAN] = "Zeit",
},
{ // LBL_STA_DESC
[LANG_ENGLISH] = "Address Orientat. Inverted",
[LANG_SPANISH] = "Direcc. Orientac. Invertido",
[LANG_CATALAN] = "Direcc. Orientacio Invertit",
[LANG_GERMAN] = "Adresse Ausricht. Invertiert",
},
};

View File

@@ -0,0 +1,885 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
*/
////////////////////////////////////////////////////////////
// ***** WINDOW OBJECTS *****
////////////////////////////////////////////////////////////
void openWindow(uint16_t id) {
uint16_t n;
char buf[MAX_LABEL_LNG];
switch (id) {
case WIN_LOGO:
createObject(OBJ_WIN, WIN_LOGO);
createObject(OBJ_DRAWSTR, DSTR_INIT);
createObject(OBJ_LABEL, LBL_PACO_TXT);
posObjStack1 = createObject(OBJ_LABEL, LBL_INIT);
createObject(OBJ_ICON, ICON_PACO);
createObject(OBJ_BAR, BAR_INIT);
drawWindow(WIN_LOGO);
break;
case WIN_CALIBRATE:
createObject(OBJ_WIN, WIN_CALIBRATE);
posObjStack2 = createObject(OBJ_LABEL, LBL_CAL);
posObjStack1 = createObject(OBJ_LABEL, LBL_PRESS);
newEvent(OBJ_WIN, WIN_CALIBRATE, EVNT_DRAW);
break;
case WIN_SSID:
createObject(OBJ_WIN, WIN_SSID);
createObject(OBJ_ICON, ICON_WIFI_SSID);
createObject(OBJ_BUTTON, BUT_SSID_CLOSE);
posObjStack1 = createObject(OBJ_LABEL, LBL_SCAN);
drawWindow(WIN_SSID);
scanWiFi();
objStack[posObjStack1].objID = LBL_SSID_SCAN;
createObject(OBJ_TXT, TXT_SSID1);
createObject(OBJ_TXT, TXT_SSID2);
createObject(OBJ_TXT, TXT_SSID3);
createObject(OBJ_TXT, TXT_SSID4);
createObject(OBJ_TXT, TXT_SSID5);
createObject(OBJ_TXT, TXT_SSID6);
scanWiFiFill();
newEvent(OBJ_WIN, WIN_SSID, EVNT_DRAW);
break;
case WIN_WIFI:
snprintf(ssidName, SSID_LNG, "%s", wifiSetting.ssid);
snprintf(keybPwdHideBuf, 9, "********");
snprintf(keybIP1Buf, 4, "%d", wifiSetting.CS_IP[0]);
snprintf(keybIP2Buf, 4, "%d", wifiSetting.CS_IP[1]);
snprintf(keybIP3Buf, 4, "%d", wifiSetting.CS_IP[2]);
snprintf(keybIP4Buf, 4, "%d", wifiSetting.CS_IP[3]);
setProtocolData();
createObject(OBJ_WIN, WIN_WIFI);
createObject(OBJ_ICON, ICON_WIFI_CFG);
createObject(OBJ_LABEL, LBL_SSID);
createObject(OBJ_TXT, TXT_SSID);
createObject(OBJ_LABEL, LBL_PWD_HIDE);
createObject(OBJ_TXT, TXT_PWD_HIDE);
createObject(OBJ_LABEL, LBL_IP);
createObject(OBJ_TXT, TXT_IP1);
createObject(OBJ_TXT, TXT_IP2);
createObject(OBJ_TXT, TXT_IP3);
createObject(OBJ_TXT, TXT_IP4);
createObject(OBJ_LABEL, LBL_PORT);
createObject(OBJ_TXT, TXT_PORT);
createObject(OBJ_KEYBOARD, KEYB_IP);
createObject(OBJ_LABEL, LBL_PROTOCOL);
createObject(OBJ_TXT, TXT_PROTOCOL);
createObject(OBJ_BUTTON, BUT_WIFI_OK);
newEvent(OBJ_WIN, WIN_WIFI, EVNT_DRAW);
break;
case WIN_WIFI_PWD:
snprintf(keybPwdBuf, PWD_LNG, wifiSetting.password);
createObject(OBJ_WIN, WIN_WIFI_PWD);
createObject(OBJ_TXT, TXT_PWD);
createObject(OBJ_KEYBOARD, KEYB_PWD);
createObject(OBJ_BUTTON, BUT_PWD_OK);
createObject(OBJ_BUTTON, BUT_PWD_CNCL);
newEvent(OBJ_WIN, WIN_WIFI_PWD, EVNT_DRAW);
break;
case WIN_PROTOCOL:
radioData[RAD_PROTOCOL].value = wifiSetting.protocol - CLIENT_Z21;
if (wifiSetting.protocol == CLIENT_LNET)
radioData[RAD_PROTOCOL_LN].value = (wifiSetting.serverType) ? 0 : 1;
else
radioData[RAD_PROTOCOL_LN].value = radioData[RAD_PROTOCOL_LN].num;
createObject(OBJ_WIN, WIN_PROTOCOL);
createObject(OBJ_RADIO, RAD_PROTOCOL);
createObject(OBJ_RADIO, RAD_PROTOCOL_LN);
createObject(OBJ_LABEL, LBL_SEL_PROT);
createObject(OBJ_LABEL, LBL_SEL_Z21);
createObject(OBJ_LABEL, LBL_SEL_XNET);
createObject(OBJ_LABEL, LBL_SEL_ECOS);
createObject(OBJ_LABEL, LBL_SEL_LNET);
createObject(OBJ_LABEL, LBL_SEL_LBSERVER);
createObject(OBJ_LABEL, LBL_SEL_BINARY);
createObject(OBJ_BUTTON, BUT_PROT_OK);
createObject(OBJ_BUTTON, BUT_OPTIONS);
newEvent(OBJ_WIN, WIN_PROTOCOL, EVNT_DRAW);
break;
case WIN_THROTTLE:
iconData[ICON_LOK_EDIT].bitmap = (wifiSetting.protocol == CLIENT_ECOS) ? info24 : wrench;
createObject(OBJ_WIN, WIN_THROTTLE);
createObject(OBJ_ICON, ICON_MENU);
createObject(OBJ_ICON, ICON_POWER);
createObject(OBJ_ICON, ICON_FNEXT);
createObject(OBJ_ICON, ICON_LOK_EDIT);
createObject(OBJ_FNC, FNC_ACC_PANEL);
//createObject(OBJ_ICON, ICON_FWD);
//createObject(OBJ_ICON, ICON_REV);
createObject(OBJ_TXT, TXT_CLOCK);
createObject(OBJ_TXT, TXT_LOCO_NAME);
createObject(OBJ_TXT, TXT_LOCO_ADDR);
createObject(OBJ_LPIC, LPIC_MAIN);
createObject(OBJ_GAUGE, GAUGE_SPEED);
createObject(OBJ_LABEL, LBL_KMH);
createObject(OBJ_FNC, FNC_FX0);
createObject(OBJ_FNC, FNC_FX1);
createObject(OBJ_FNC, FNC_FX2);
createObject(OBJ_FNC, FNC_FX3);
createObject(OBJ_FNC, FNC_FX4);
createObject(OBJ_FNC, FNC_FX5);
createObject(OBJ_FNC, FNC_FX6);
createObject(OBJ_FNC, FNC_FX7);
createObject(OBJ_FNC, FNC_FX8);
createObject(OBJ_FNC, FNC_FX9);
newEvent(OBJ_WIN, WIN_THROTTLE, EVNT_DRAW);
break;
case WIN_CONFIG:
buttonData[BUT_CFG_I_LANG].objID = DSTR_ENGLISH + currLanguage;
lastLanguage = currLanguage;
createObject(OBJ_WIN, WIN_CONFIG);
createObject(OBJ_DRAWSTR, DSTR_CFG_MENU);
createObject(OBJ_BUTTON, BUT_CFG_I_LANG);
createObject(OBJ_BUTTON, BUT_CFG_T_LANG);
createObject(OBJ_BUTTON, BUT_CFG_I_SCR);
createObject(OBJ_BUTTON, BUT_CFG_T_SCR);
createObject(OBJ_BUTTON, BUT_CFG_I_SPD);
createObject(OBJ_BUTTON, BUT_CFG_T_SPD);
createObject(OBJ_BUTTON, BUT_CFG_I_WIFI);
createObject(OBJ_BUTTON, BUT_CFG_T_WIFI);
createObject(OBJ_BUTTON, BUT_CFG_I_FCLK);
createObject(OBJ_BUTTON, BUT_CFG_T_FCLK);
createObject(OBJ_BUTTON, BUT_CFG_I_LOCK);
createObject(OBJ_BUTTON, BUT_CFG_T_LOCK);
createObject(OBJ_BUTTON, BUT_CFG_I_ABOUT);
createObject(OBJ_BUTTON, BUT_CFG_T_ABOUT);
createObject(OBJ_ICON, ICON_CFG_EXIT);
newEvent(OBJ_WIN, WIN_CONFIG, EVNT_DRAW);
break;
case WIN_SCREEN:
barData[BAR_BLIGHT].value = backlight;
switchData[SW_ROTATE].state = (locationUSB == USB_UP);
createObject(OBJ_WIN, WIN_SCREEN);
createObject(OBJ_ICON, ICON_BLIGHT);
createObject(OBJ_BAR, BAR_BLIGHT);
createObject(OBJ_SWITCH, SW_ROTATE);
createObject(OBJ_LABEL, LBL_SCR_ROTATE);
createObject(OBJ_BUTTON, BUT_CFG_TOUCH);
createObject(OBJ_BUTTON, BUT_SCR_OK);
createObject(OBJ_BUTTON, BUT_SCR_CNCL);
newEvent(OBJ_WIN, WIN_SCREEN, EVNT_DRAW);
break;
case WIN_SPEED:
switchData[SW_SHUNTING].state = shuntingMode;
radioData[RAD_STOP_MODE].value = (stopMode > 0) ? 1 : 0;
createObject(OBJ_WIN, WIN_SPEED);
createObject(OBJ_LABEL, LBL_SHUNTING);
createObject(OBJ_SWITCH, SW_SHUNTING);
createObject(OBJ_RADIO, RAD_STOP_MODE);
createObject(OBJ_ICON, ICON_STOP);
createObject(OBJ_LABEL, LBL_STOP_0);
createObject(OBJ_LABEL, LBL_STOP_E);
createObject(OBJ_BUTTON, BUT_SPD_OK);
newEvent(OBJ_WIN, WIN_SPEED, EVNT_DRAW);
break;
case WIN_SET_CLOCK:
snprintf(keybHourBuf, 3, "%d", clockHour);
snprintf(keybMinBuf, 3, "%d", clockMin);
snprintf(keybRateBuf, 4, "%d", clockRate);
showClockData(TXT_HOUR);
createObject(OBJ_WIN, WIN_SET_CLOCK);
createObject(OBJ_DRAWSTR, DSTR_CLOCK);
createObject(OBJ_CHAR, CHAR_CLK_COLON);
createObject(OBJ_ICON, ICON_SET_CLOCK);
createObject(OBJ_TXT, TXT_HOUR);
createObject(OBJ_TXT, TXT_MIN);
createObject(OBJ_TXT, TXT_RATE);
createObject(OBJ_LABEL, LBL_RATE);
createObject(OBJ_KEYBOARD, KEYB_CLOCK);
createObject(OBJ_BUTTON, BUT_CLOCK_OK);
createObject(OBJ_BUTTON, BUT_CLOCK_CNCL);
newEvent(OBJ_WIN, WIN_SET_CLOCK, EVNT_DRAW);
break;
case WIN_LOCK:
switchData[SW_LOCK_LOK].state = (bitRead(lockOptions, LOCK_SEL_LOCO)) ? true : false;
switchData[SW_LOCK_ACC].state = (bitRead(lockOptions, LOCK_TURNOUT)) ? true : false;
switchData[SW_LOCK_PRG].state = (bitRead(lockOptions, LOCK_PROG)) ? true : false;
createObject(OBJ_WIN, WIN_LOCK);
createObject(OBJ_SWITCH, SW_LOCK_LOK);
createObject(OBJ_SWITCH, SW_LOCK_ACC);
createObject(OBJ_SWITCH, SW_LOCK_PRG);
createObject(OBJ_LABEL, LBL_LOCK_LOK);
createObject(OBJ_LABEL, LBL_LOCK_ACC);
createObject(OBJ_LABEL, LBL_LOCK_PRG);
createObject(OBJ_BUTTON, BUT_LOCK);
newEvent(OBJ_WIN, WIN_LOCK, EVNT_DRAW);
break;
case WIN_ABOUT:
snprintf (aboutPacoMouseCYD, PWD_LNG + 1, "v%s.%s%s", VER_H, VER_L, VER_R);
snprintf (aboutIP, PWD_LNG + 1, "IP: %u.%u.%u.%u", WiFi.localIP().operator[](0), WiFi.localIP().operator[](1), WiFi.localIP().operator[](2), WiFi.localIP().operator[](3));
snprintf (aboutMAC, PWD_LNG + 1, "MAC: %s", WiFi.macAddress().c_str());
createObject(OBJ_WIN, WIN_ABOUT);
createObject(OBJ_DRAWSTR, DSTR_ABOUT);
createObject(OBJ_LABEL, LBL_PACO_TXT);
createObject(OBJ_ICON, ICON_ABOUT_PACO);
createObject(OBJ_TXT, TXT_ABOUT);
createObject(OBJ_TXT, TXT_ABOUT_IP);
createObject(OBJ_TXT, TXT_ABOUT_MAC);
createObject(OBJ_LABEL, LBL_PACO_WEB);
newEvent(OBJ_WIN, WIN_ABOUT, EVNT_DRAW);
break;
case WIN_LOK_EDIT:
snprintf (locoEditName, NAME_LNG + 1, "%s", locoData[myLocoData].myName );
sprintf (locoEditAddr, "%d", locoData[myLocoData].myAddr.address);
sprintf (locoEditID, "%d", locoData[myLocoData].myLocoID);
sprintf (locoEditVmax, "%d", locoData[myLocoData].myVmax);
lpicData[LPIC_LOK_EDIT].id = locoData[myLocoData].myLocoID;
for (n = 0; n < 29; n++)
fncData[FNC_F0 + n].idIcon = locoData[myLocoData].myFuncIcon[n];
createObject(OBJ_WIN, WIN_LOK_EDIT);
createObject(OBJ_LABEL, LBL_ADDR);
createObject(OBJ_LABEL, LBL_IMAGE);
createObject(OBJ_LABEL, LBL_NAME);
createObject(OBJ_LABEL, LBL_VMAX);
createObject(OBJ_LPIC, LPIC_LOK_EDIT);
createObject(OBJ_TXT, TXT_EDIT_ADDR);
createObject(OBJ_TXT, TXT_EDIT_NAME);
createObject(OBJ_TXT, TXT_EDIT_IMAGE);
createObject(OBJ_TXT, TXT_EDIT_VMAX);
createObject(OBJ_BUTTON, BUT_EDIT_FUNC);
createObject(OBJ_BUTTON, BUT_EDIT_CNCL);
if (wifiSetting.protocol != CLIENT_ECOS) {
createObject(OBJ_BUTTON, BUT_EDIT_OK);
if ((locoData[myLocoData].mySpeed < 2) && (countLocoInStack() > 1)) // stopped and remaining locos in stack
createObject(OBJ_BUTTON, BUT_EDIT_DEL);
}
newEvent(OBJ_WIN, WIN_LOK_EDIT, EVNT_DRAW);
break;
case WIN_EDIT_NAME:
snprintf(keybNameBuf, NAME_LNG + 1, locoData[myLocoData].myName);
txtData[TXT_NAME].maxLength = NAME_LNG;
createObject(OBJ_WIN, WIN_EDIT_NAME);
createObject(OBJ_TXT, TXT_NAME);
createObject(OBJ_KEYBOARD, KEYB_NAME);
createObject(OBJ_BUTTON, BUT_NAME_OK);
createObject(OBJ_BUTTON, BUT_NAME_CNCL);
newEvent(OBJ_WIN, WIN_EDIT_NAME, EVNT_DRAW);
break;
case WIN_FUNC:
createObject(OBJ_WIN, WIN_FUNC);
for (n = 0; n < 29; n++)
createObject(OBJ_FNC, FNC_F0 + n);
createObject(OBJ_LABEL, LBL_ADDR);
createObject(OBJ_TXT, TXT_EDIT_ADDR);
createObject(OBJ_LABEL, LBL_EDIT_FUNC);
if (wifiSetting.protocol != CLIENT_ECOS)
createObject(OBJ_BUTTON, BUT_FNC_OK);
createObject(OBJ_BUTTON, BUT_FNC_CNCL);
newEvent(OBJ_WIN, WIN_FUNC, EVNT_DRAW);
break;
case WIN_CHG_FUNC:
createObject(OBJ_WIN, WIN_CHG_FUNC);
createObject(OBJ_FNC, FNC_CHG);
createObject(OBJ_TXT, TXT_EDIT_FNC);
newEvent(OBJ_WIN, WIN_CHG_FUNC, EVNT_DRAW);
break;
case WIN_VMAX:
createObject(OBJ_WIN, WIN_VMAX);
createObject(OBJ_TXT, TXT_KEYB_VMAX);
createObject(OBJ_KEYBOARD, KEYB_VMAX);
newEvent(OBJ_WIN, WIN_VMAX, EVNT_DRAW);
break;
case WIN_SEL_LOCO:
createObject(OBJ_WIN, WIN_SEL_LOCO);
createObject(OBJ_DRAWSTR, DSTR_SELLOK);
posObjStack1 = createObject(OBJ_ICON, ICON_LAST_UP);
prepareLocoList();
createObject(OBJ_ICON, ICON_SEL_LOK);
if (wifiSetting.protocol != CLIENT_ECOS)
createObject(OBJ_FNC, FNC_SEL_KEYPAD);
createObject(OBJ_TXT, TXT_SEL_ADDR1);
createObject(OBJ_TXT, TXT_SEL_NAME1);
createObject(OBJ_TXT, TXT_SEL_ADDR2);
createObject(OBJ_TXT, TXT_SEL_NAME2);
createObject(OBJ_TXT, TXT_SEL_ADDR3);
createObject(OBJ_TXT, TXT_SEL_NAME3);
createObject(OBJ_TXT, TXT_SEL_ADDR4);
createObject(OBJ_TXT, TXT_SEL_NAME4);
createObject(OBJ_TXT, TXT_SEL_ADDR5);
createObject(OBJ_TXT, TXT_SEL_NAME5);
createObject(OBJ_TXT, TXT_SEL_ADDR6);
createObject(OBJ_TXT, TXT_SEL_NAME6);
newEvent(OBJ_WIN, WIN_SEL_LOCO, EVNT_DRAW);
break;
case WIN_ENTER_ADDR:
locoKeybAddr[0] = '\0';
createObject(OBJ_WIN, WIN_ENTER_ADDR);
createObject(OBJ_TXT, TXT_KEYB_ADDR);
createObject(OBJ_KEYBOARD, KEYB_ADDR);
newEvent(OBJ_WIN, WIN_ENTER_ADDR, EVNT_DRAW);
break;
case WIN_SEL_IMAGE:
createObject(OBJ_WIN, WIN_SEL_IMAGE);
createObject(OBJ_LABEL, LBL_SEL_IMAGE);
createObject(OBJ_BUTTON, BUT_IMAGE_CNCL);
drawWindow(WIN_SEL_IMAGE);
populateImageList();
createObject(OBJ_LPIC, LPIC_SEL_IMG1);
createObject(OBJ_LPIC, LPIC_SEL_IMG2);
createObject(OBJ_LPIC, LPIC_SEL_IMG3);
createObject(OBJ_LPIC, LPIC_SEL_IMG4);
createObject(OBJ_LPIC, LPIC_SEL_IMG5);
createObject(OBJ_LPIC, LPIC_SEL_IMG6);
createObject(OBJ_ICON, ICON_PREV_IMAGE);
createObject(OBJ_ICON, ICON_NEXT_IMAGE);
newEvent(OBJ_WIN, WIN_SEL_IMAGE, EVNT_DRAW);
break;
case WIN_MENU:
createObject(OBJ_WIN, WIN_MENU);
createObject(OBJ_BUTTON, BUT_MENU_I_DRIVE);
createObject(OBJ_BUTTON, BUT_MENU_T_DRIVE);
createObject(OBJ_BUTTON, BUT_MENU_I_ACC);
createObject(OBJ_BUTTON, BUT_MENU_T_ACC);
createObject(OBJ_BUTTON, BUT_MENU_I_CV);
createObject(OBJ_BUTTON, BUT_MENU_T_CV);
createObject(OBJ_BUTTON, BUT_MENU_I_CFG);
createObject(OBJ_BUTTON, BUT_MENU_T_CFG);
createObject(OBJ_BUTTON, BUT_MENU_I_UTILS);
createObject(OBJ_BUTTON, BUT_MENU_T_UTILS);
createObject(OBJ_DRAWSTR, DSTR_MENU);
newEvent(OBJ_WIN, WIN_MENU, EVNT_DRAW);
break;
case WIN_OPTIONS:
setOptionsData();
createObject(OBJ_WIN, WIN_OPTIONS);
switch (wifiSetting.protocol) {
case CLIENT_Z21:
//createObject(OBJ_SWITCH, SW_OPT_TT_OFFSET);
//createObject(OBJ_LABEL, LBL_OPT_TT_OFFSET);
createObject(OBJ_SWITCH, SW_OPT_ADR);
createObject(OBJ_LABEL, LBL_OPT_ADR);
break;
case CLIENT_XNET:
//createObject(OBJ_SWITCH, SW_OPT_TT_OFFSET);
//createObject(OBJ_LABEL, LBL_OPT_TT_OFFSET);
break;
case CLIENT_LNET:
switchData[SW_OPT_DISCOVER].state = (autoIdentifyCS > 0) ? true : false;
createObject(OBJ_SWITCH, SW_OPT_DISCOVER);
createObject(OBJ_LABEL, LBL_OPT_DISCOVER);
createObject(OBJ_RADIO, RAD_CSTATION);
createObject(OBJ_LABEL, LBL_OPT_IB2);
createObject(OBJ_LABEL, LBL_OPT_UHLI);
createObject(OBJ_LABEL, LBL_OPT_DIG);
break;
case CLIENT_ECOS:
break;
}
createObject(OBJ_BUTTON, BUT_OPT_OK);
newEvent(OBJ_WIN, WIN_OPTIONS, EVNT_DRAW);
break;
case WIN_SPEEDO:
speedoSpeed = 0;
speedoPhase = SPD_WAIT;
setSpeedoPhase(SPD_WAIT);
setTextSpeedo();
snprintf(spdLengthBuf, NAME_LNG + 1, "%d", speedoLength);
snprintf(spdSpeedBuf, NAME_LNG + 1, "%d km/h", speedoSpeed);
iconData[ICON_SPEEDO_LOK].x = 40 + (speedoPhase * 32);
drawStrData[DSTR_SPEEDO_BLANK].x = 40 + (speedoPhase * 32);
lpicData[LPIC_SPEEDO].id = locoData[myLocoData].myLocoID;
createObject(OBJ_WIN, WIN_SPEEDO);
createObject(OBJ_LPIC, LPIC_SPEEDO);
createObject(OBJ_LABEL, LBL_SCALE);
createObject(OBJ_LABEL, LBL_MM);
createObject(OBJ_GAUGE, GAUGE_SPEEDO);
createObject(OBJ_FNC, FNC_SPEEDO_DIR);
createObject(OBJ_DRAWSTR, DSTR_SPEEDO_BLANK);
createObject(OBJ_DRAWSTR, DSTR_SPEEDO_TRK);
createObject(OBJ_ICON, ICON_SPEEDO_LOK);
createObject(OBJ_ICON, ICON_SPEEDO_RADAR);
createObject(OBJ_BUTTON, BUT_SPEEDO_CNCL);
createObject(OBJ_BUTTON, BUT_SPEEDO_CV);
createObject(OBJ_TXT, TXT_SPEEDO_SCALE);
createObject(OBJ_TXT, TXT_SPEEDO_LNG);
createObject(OBJ_TXT, TXT_SPEEDO_SPD);
newEvent(OBJ_WIN, WIN_SPEEDO, EVNT_DRAW);
break;
case WIN_SPEEDO_LNG:
snprintf(speedoKeybLng, PORT_LNG + 1, "%d", speedoLength);
createObject(OBJ_WIN, WIN_SPEEDO_LNG);
createObject(OBJ_TXT, TXT_EDIT_LNG);
createObject(OBJ_KEYBOARD, KEYB_LNG);
newEvent(OBJ_WIN, WIN_SPEEDO_LNG, EVNT_DRAW);
break;
case WIN_SPEEDO_SCALE:
setTextSpeedo();
createObject(OBJ_WIN, WIN_SPEEDO_SCALE);
createObject(OBJ_TXT, TXT_EDIT_SCALE);
createObject(OBJ_TXT, TXT_NUM_SCALE);
createObject(OBJ_KEYBOARD, KEYB_SCALE);
createObject(OBJ_BUTTON, BUT_SPEEDO_H0);
createObject(OBJ_BUTTON, BUT_SPEEDO_N);
createObject(OBJ_BUTTON, BUT_SPEEDO_TT);
createObject(OBJ_BUTTON, BUT_SPEEDO_Z);
createObject(OBJ_BUTTON, BUT_SPEEDO_0);
newEvent(OBJ_WIN, WIN_SPEEDO_SCALE, EVNT_DRAW);
break;
case WIN_READ_CV:
createObject(OBJ_WIN, WIN_READ_CV);
createObject(OBJ_DRAWSTR, DSTR_CFG_MENU);
createObject(OBJ_BUTTON, BUT_CV_ADDR);
createObject(OBJ_BUTTON, BUT_CV_SPD_L);
createObject(OBJ_BUTTON, BUT_CV_SPD_M);
createObject(OBJ_BUTTON, BUT_CV_SPD_H);
createObject(OBJ_BUTTON, BUT_CV_ACC);
createObject(OBJ_BUTTON, BUT_CV_DEC);
createObject(OBJ_BUTTON, BUT_CV_CFG);
createObject(OBJ_BUTTON, BUT_CV_MAN);
newEvent(OBJ_WIN, WIN_READ_CV, EVNT_DRAW);
break;
case WIN_PROG_CV:
//buttonData[BUT_CV_LNCV].backgnd = (wifiSetting.protocol == CLIENT_LNET) ? COLOR_CREAM : COLOR_LIGHTBLACK;
setFieldsCV();
setBitsCV();
setStatusCV();
switchData[SW_POM].state = modeProg;
createObject(OBJ_WIN, WIN_PROG_CV);
createObject(OBJ_LABEL, LBL_CV);
createObject(OBJ_LABEL, LBL_POM);
createObject(OBJ_LABEL, LBL_BITS);
createObject(OBJ_SWITCH, SW_POM);
createObject(OBJ_BUTTON, BUT_CV_READ);
createObject(OBJ_BUTTON, BUT_CV_CNCL);
if (wifiSetting.protocol == CLIENT_LNET)
createObject(OBJ_BUTTON, BUT_CV_LNCV);
createObject(OBJ_KEYBOARD, KEYB_CV);
createObject(OBJ_CHAR, CHAR_CV_EQUAL);
createObject(OBJ_BUTTON, BUT_CV_0);
createObject(OBJ_BUTTON, BUT_CV_1);
createObject(OBJ_BUTTON, BUT_CV_2);
createObject(OBJ_BUTTON, BUT_CV_3);
createObject(OBJ_BUTTON, BUT_CV_4);
createObject(OBJ_BUTTON, BUT_CV_5);
createObject(OBJ_BUTTON, BUT_CV_6);
createObject(OBJ_BUTTON, BUT_CV_7);
createObject(OBJ_TXT, TXT_CV);
createObject(OBJ_TXT, TXT_CV_VAL);
createObject(OBJ_TXT, TXT_CV_STATUS);
newEvent(OBJ_WIN, WIN_PROG_CV, EVNT_DRAW);
break;
case WIN_PROG_ADDR:
getLabelTxt(LBL_CV_ADDR, buf);
snprintf(cvStatusBuf, PWD_LNG + 1, "%s", buf);
createObject(OBJ_WIN, WIN_PROG_ADDR);
createObject(OBJ_TXT, TXT_CV_STATUS);
createObject(OBJ_ICON, ICON_ADDR);
createObject(OBJ_TXT, TXT_CV_ADDR);
createObject(OBJ_KEYBOARD, KEYB_CV_ADDR);
createObject(OBJ_BUTTON, BUT_ADDR_CNCL);
newEvent(OBJ_WIN, WIN_PROG_ADDR, EVNT_DRAW);
break;
case WIN_PROG_LNCV:
setFieldsLNCV();
createObject(OBJ_WIN, WIN_PROG_LNCV);
createObject(OBJ_KEYBOARD, KEYB_LNCV);
createObject(OBJ_LABEL, LBL_LNCV_ART);
createObject(OBJ_LABEL, LBL_LNCV_MOD);
createObject(OBJ_LABEL, LBL_LNCV_NUM);
createObject(OBJ_BUTTON, BUT_LNCV_FIND);
createObject(OBJ_BUTTON, BUT_LNCV_CNCL);
createObject(OBJ_TXT, TXT_LNCV_ART);
createObject(OBJ_TXT, TXT_LNCV_MOD);
createObject(OBJ_TXT, TXT_LNCV_ADR);
createObject(OBJ_TXT, TXT_LNCV_VAL);
createObject(OBJ_CHAR, CHAR_LNCV_EQUAL);
newEvent(OBJ_WIN, WIN_PROG_LNCV, EVNT_DRAW);
break;
case WIN_STEAM:
fncData[FNC_ST_SMOKE].state = false;
createObject(OBJ_WIN, WIN_STEAM);
createObject(OBJ_DRAWSTR, DSTR_STEAM);
createObject(OBJ_ICON, ICON_POWER);
createObject(OBJ_ICON, ICON_MANOMETER);
createObject(OBJ_ICON, ICON_STEAM_EDIT);
createObject(OBJ_BUTTON, BUT_STEAM_CNCL);
createObject(OBJ_FNC, FNC_ST_WATER);
createObject(OBJ_FNC, FNC_ST_TENDER);
createObject(OBJ_FNC, FNC_ST_WHISTLE);
createObject(OBJ_FNC, FNC_ST_FIRE);
createObject(OBJ_FNC, FNC_ST_SMOKE);
createObject(OBJ_BAR, BAR_JOHNSON);
createObject(OBJ_BAR, BAR_WATER);
createObject(OBJ_BAR, BAR_TENDER);
createObject(OBJ_BAR, BAR_BRAKE);
newEvent(OBJ_WIN, WIN_STEAM, EVNT_DRAW);
break;
case WIN_UTIL:
createObject(OBJ_WIN, WIN_UTIL);
createObject(OBJ_BUTTON, BUT_UTL_I_SPEEDO);
createObject(OBJ_BUTTON, BUT_UTL_T_SPEEDO);
createObject(OBJ_BUTTON, BUT_UTL_I_STEAM);
createObject(OBJ_BUTTON, BUT_UTL_T_STEAM);
createObject(OBJ_BUTTON, BUT_UTL_I_SCAN);
createObject(OBJ_BUTTON, BUT_UTL_T_SCAN);
createObject(OBJ_BUTTON, BUT_UTL_I_STA);
createObject(OBJ_BUTTON, BUT_UTL_T_STA);
createObject(OBJ_ICON, ICON_UTL_EXIT);
createObject(OBJ_DRAWSTR, DSTR_UTL_MENU);
newEvent(OBJ_WIN, WIN_UTIL, EVNT_DRAW);
break;
case WIN_ACCESSORY:
editAccessory = false;
winData[WIN_ACCESSORY].backgnd = COLOR_WHITE;
updateAccPanel();
createObject(OBJ_WIN, WIN_ACCESSORY);
createObject(OBJ_BUTTON, BUT_ACC_0);
createObject(OBJ_BUTTON, BUT_ACC_1);
createObject(OBJ_BUTTON, BUT_ACC_2);
createObject(OBJ_BUTTON, BUT_ACC_3);
createObject(OBJ_BUTTON, BUT_ACC_4);
createObject(OBJ_BUTTON, BUT_ACC_5);
createObject(OBJ_BUTTON, BUT_ACC_6);
createObject(OBJ_BUTTON, BUT_ACC_7);
createObject(OBJ_BUTTON, BUT_ACC_8);
createObject(OBJ_BUTTON, BUT_ACC_9);
createObject(OBJ_BUTTON, BUT_ACC_10);
createObject(OBJ_BUTTON, BUT_ACC_11);
createObject(OBJ_BUTTON, BUT_ACC_12);
createObject(OBJ_BUTTON, BUT_ACC_13);
createObject(OBJ_BUTTON, BUT_ACC_14);
createObject(OBJ_BUTTON, BUT_ACC_15);
createObject(OBJ_BUTTON, BUT_ACC_CNCL);
createObject(OBJ_BUTTON, BUT_ACC_EDIT);
createObject(OBJ_TXT, TXT_ACC_0);
createObject(OBJ_TXT, TXT_ACC_1);
createObject(OBJ_TXT, TXT_ACC_2);
createObject(OBJ_TXT, TXT_ACC_3);
createObject(OBJ_TXT, TXT_ACC_4);
createObject(OBJ_TXT, TXT_ACC_5);
createObject(OBJ_TXT, TXT_ACC_6);
createObject(OBJ_TXT, TXT_ACC_7);
createObject(OBJ_TXT, TXT_ACC_8);
createObject(OBJ_TXT, TXT_ACC_9);
createObject(OBJ_TXT, TXT_ACC_10);
createObject(OBJ_TXT, TXT_ACC_11);
createObject(OBJ_TXT, TXT_ACC_12);
createObject(OBJ_TXT, TXT_ACC_13);
createObject(OBJ_TXT, TXT_ACC_14);
createObject(OBJ_TXT, TXT_ACC_15);
createObject(OBJ_TXT, TXT_PANEL);
newEvent(OBJ_WIN, WIN_ACCESSORY, EVNT_DRAW);
break;
case WIN_PANELS:
createObject(OBJ_WIN, WIN_PANELS);
createObject(OBJ_TXT, TXT_PANEL0);
createObject(OBJ_TXT, TXT_PANEL1);
createObject(OBJ_TXT, TXT_PANEL2);
createObject(OBJ_TXT, TXT_PANEL3);
createObject(OBJ_TXT, TXT_PANEL4);
createObject(OBJ_TXT, TXT_PANEL5);
createObject(OBJ_TXT, TXT_PANEL6);
createObject(OBJ_TXT, TXT_PANEL7);
createObject(OBJ_TXT, TXT_PANEL8);
createObject(OBJ_TXT, TXT_PANEL9);
createObject(OBJ_TXT, TXT_PANEL10);
createObject(OBJ_TXT, TXT_PANEL11);
createObject(OBJ_TXT, TXT_PANEL12);
createObject(OBJ_TXT, TXT_PANEL13);
createObject(OBJ_TXT, TXT_PANEL14);
createObject(OBJ_TXT, TXT_PANEL15);
newEvent(OBJ_WIN, WIN_PANELS, EVNT_DRAW);
break;
case WIN_PANEL_NAME:
snprintf(keybNameBuf, PANEL_LNG + 1, panelNameBuf);
txtData[TXT_NAME].maxLength = PANEL_LNG;
createObject(OBJ_WIN, WIN_PANEL_NAME);
createObject(OBJ_TXT, TXT_NAME);
createObject(OBJ_KEYBOARD, KEYB_NAME);
createObject(OBJ_BUTTON, BUT_NAME_OK);
createObject(OBJ_BUTTON, BUT_NAME_CNCL);
newEvent(OBJ_WIN, WIN_PANEL_NAME, EVNT_DRAW);
break;
case WIN_ACC_CTRL:
snprintf(accKeybAddr, ADDR_LNG + 1, "%d", myTurnout);
createObject(OBJ_WIN, WIN_ACC_CTRL);
createObject(OBJ_TXT, TXT_ACC_ADDR);
createObject(OBJ_KEYBOARD, KEYB_ACC);
createObject(OBJ_ICON, ICON_KEYB_ACC);
createObject(OBJ_BUTTON, BUT_ACC_RED);
createObject(OBJ_BUTTON, BUT_ACC_GREEN);
newEvent(OBJ_WIN, WIN_ACC_CTRL, EVNT_DRAW);
break;
case WIN_ACC_ASPECT:
createObject(OBJ_WIN, WIN_ACC_ASPECT);
createObject(OBJ_BUTTON, BUT_ACC_ASPECT0);
createObject(OBJ_BUTTON, BUT_ACC_ASPECT1);
createObject(OBJ_BUTTON, BUT_ACC_ASPECT2);
if (currAccAspects == 4)
createObject(OBJ_BUTTON, BUT_ACC_ASPECT3);
newEvent(OBJ_WIN, WIN_ACC_ASPECT, EVNT_DRAW);
break;
case WIN_ACC_TYPE:
createObject(OBJ_WIN, WIN_ACC_TYPE);
createObject(OBJ_LABEL, LBL_ACC_TYPE);
createObject(OBJ_FNC, FNC_ACC_TYPE);
newEvent(OBJ_WIN, WIN_ACC_TYPE, EVNT_DRAW);
break;
case WIN_ACC_EDIT:
n = accDef[currAccEdit.type].aspects;
winData[WIN_ACC_EDIT].h = 130 + (n * 40);
buttonData[BUT_TYPE_OK].y = 93 + (n * 40);
buttonData[BUT_TYPE_CNCL].y = 93 + (n * 40);
iconData[ICON_TYPE_OK].y = 97 + (n * 40);
iconData[ICON_TYPE_CNCL].y = 97 + (n * 40);
createObject(OBJ_WIN, WIN_ACC_EDIT);
createObject(OBJ_LABEL, LBL_ACC_NAME);
createObject(OBJ_LABEL, LBL_ACC_ADDR);
createObject(OBJ_TXT, TXT_ACC_NAME);
createObject(OBJ_TXT, TXT_ACC_ADDR1);
createObject(OBJ_FNC, FNC_EDIT_ASPECT0);
createObject(OBJ_BUTTON, BUT_ACC_OUT0);
createObject(OBJ_BUTTON, BUT_ACC_OUT1);
if (n > 1) {
createObject(OBJ_FNC, FNC_EDIT_ASPECT1);
createObject(OBJ_BUTTON, BUT_ACC_OUT4);
createObject(OBJ_BUTTON, BUT_ACC_OUT5);
}
if (n > 2) {
createObject(OBJ_TXT, TXT_ACC_ADDR2);
createObject(OBJ_ICON, ICON_PLUS_ONE);
createObject(OBJ_FNC, FNC_EDIT_ASPECT2);
createObject(OBJ_BUTTON, BUT_ACC_OUT2);
createObject(OBJ_BUTTON, BUT_ACC_OUT3);
createObject(OBJ_BUTTON, BUT_ACC_OUT6);
createObject(OBJ_BUTTON, BUT_ACC_OUT7);
createObject(OBJ_BUTTON, BUT_ACC_OUT8);
createObject(OBJ_BUTTON, BUT_ACC_OUT9);
createObject(OBJ_BUTTON, BUT_ACC_OUT10);
createObject(OBJ_BUTTON, BUT_ACC_OUT11);
}
if (n > 3) {
createObject(OBJ_FNC, FNC_EDIT_ASPECT3);
createObject(OBJ_BUTTON, BUT_ACC_OUT12);
createObject(OBJ_BUTTON, BUT_ACC_OUT13);
createObject(OBJ_BUTTON, BUT_ACC_OUT14);
createObject(OBJ_BUTTON, BUT_ACC_OUT15);
}
createObject(OBJ_BUTTON, BUT_TYPE_OK);
createObject(OBJ_BUTTON, BUT_TYPE_CNCL);
newEvent(OBJ_WIN, WIN_ACC_EDIT, EVNT_DRAW);
break;
case WIN_ACC_NAME:
snprintf(keybNameBuf, ACC_LNG + 1, accKeybName);
txtData[TXT_NAME].maxLength = ACC_LNG;
createObject(OBJ_WIN, WIN_ACC_NAME);
createObject(OBJ_TXT, TXT_NAME);
createObject(OBJ_KEYBOARD, KEYB_NAME);
createObject(OBJ_BUTTON, BUT_NAME_OK);
createObject(OBJ_BUTTON, BUT_NAME_CNCL);
newEvent(OBJ_WIN, WIN_ACC_NAME, EVNT_DRAW);
break;
case WIN_ACC_ADDR1:
snprintf(accKeybAdrEdit, ADDR_LNG + 1, "%d", currAccEdit.addr);
createObject(OBJ_WIN, WIN_ACC_ADDR1);
createObject(OBJ_TXT, TXT_ACC_EDIT);
createObject(OBJ_KEYBOARD, KEYB_ACC_ADDR);
newEvent(OBJ_WIN, WIN_ACC_ADDR1, EVNT_DRAW);
break;
case WIN_ACC_ADDR2:
snprintf(accKeybAdrEdit, ADDR_LNG + 1, "%d", currAccEdit.addr2);
createObject(OBJ_WIN, WIN_ACC_ADDR2);
createObject(OBJ_TXT, TXT_ACC_EDIT);
createObject(OBJ_KEYBOARD, KEYB_ACC_ADDR);
newEvent(OBJ_WIN, WIN_ACC_ADDR2, EVNT_DRAW);
break;
case WIN_WIFI_SCAN:
setTimer(TMR_SCAN, 5, TMR_ONESHOT);
createObject(OBJ_WIN, WIN_WIFI_SCAN);
createObject(OBJ_DRAWSTR, DSTR_WIFI_SCAN);
createObject(OBJ_LABEL, LBL_SSID_SCAN);
createObject(OBJ_FNC, FNC_SCAN_RESET);
newEvent(OBJ_WIN, WIN_WIFI_SCAN, EVNT_DRAW);
break;
case WIN_STA_RUN:
updateStationTime(staTime);
updateStationLevel();
updateStationStars();
updateTargetStations();
createObject(OBJ_WIN, WIN_STA_RUN);
createObject(OBJ_LABEL, LBL_STA_RUN);
createObject(OBJ_LABEL, LBL_STA_LEVEL);
createObject(OBJ_LABEL, LBL_STA_INSTR);
createObject(OBJ_FNC, FNC_STA_STARS);
createObject(OBJ_ICON, ICON_STA_CLOCK);
createObject(OBJ_ICON, ICON_STA_STATION);
createObject(OBJ_ICON, ICON_STA_EDIT);
createObject(OBJ_BUTTON, BUT_STA_START);
createObject(OBJ_BUTTON, BUT_STA_CNCL);
createObject(OBJ_TXT, TXT_STA_LEVEL);
createObject(OBJ_TXT, TXT_STA_STARS);
createObject(OBJ_TXT, TXT_STA_STATION);
createObject(OBJ_TXT, TXT_STA_CLOCK);
newEvent(OBJ_WIN, WIN_STA_RUN, EVNT_DRAW);
break;
case WIN_STA_PLAY:
updateTurnoutButtons();
fncData[FNC_STA_RAYO].state = isTrackOff();
createObject(OBJ_WIN, WIN_STA_PLAY);
createObject(OBJ_DRAWSTR, DSTR_STATION_PLAY);
createObject(OBJ_ICON, ICON_STA_TARGET);
createObject(OBJ_ICON, ICON_STA_TRAIN);
createObject(OBJ_ICON, ICON_STA_PIN);
createObject(OBJ_ICON, ICON_STA_TIME);
createObject(OBJ_ICON, ICON_STA_COUNT);
createObject(OBJ_TXT, TXT_STA_TIME);
createObject(OBJ_TXT, TXT_STA_COUNT);
createObject(OBJ_TXT, TXT_STA_STARC);
createObject(OBJ_GAUGE, GAUGE_STATION);
createObject(OBJ_FNC, FNC_STA_DIR);
createObject(OBJ_FNC, FNC_STA_STARC);
createObject(OBJ_FNC, FNC_STA_RAYO);
createObject(OBJ_BUTTON, BUT_STA_STOP);
switch (staMaxTurnout) {
case 1:
fncData[FNC_STA_ACC0].x = 104;
buttonData[BUT_STA_ACC0].x = 100;
createObject(OBJ_BUTTON, BUT_STA_ACC0);
break;
case 2:
fncData[FNC_STA_ACC0].x = 54;
fncData[FNC_STA_ACC1].x = 154;
buttonData[BUT_STA_ACC0].x = 50;
buttonData[BUT_STA_ACC1].x = 150;
createObject(OBJ_BUTTON, BUT_STA_ACC0);
createObject(OBJ_BUTTON, BUT_STA_ACC1);
break;
case 3:
fncData[FNC_STA_ACC0].x = 40;
fncData[FNC_STA_ACC1].x = 104;
fncData[FNC_STA_ACC2].x = 168;
buttonData[BUT_STA_ACC0].x = 36;
buttonData[BUT_STA_ACC1].x = 100;
buttonData[BUT_STA_ACC2].x = 164;
createObject(OBJ_BUTTON, BUT_STA_ACC0);
createObject(OBJ_BUTTON, BUT_STA_ACC1);
createObject(OBJ_BUTTON, BUT_STA_ACC2);
break;
default:
fncData[FNC_STA_ACC0].x = 20;
fncData[FNC_STA_ACC1].x = 76;
fncData[FNC_STA_ACC2].x = 132;
fncData[FNC_STA_ACC3].x = 188;
buttonData[BUT_STA_ACC0].x = 16;
buttonData[BUT_STA_ACC1].x = 72;
buttonData[BUT_STA_ACC2].x = 128;
buttonData[BUT_STA_ACC3].x = 184;
createObject(OBJ_BUTTON, BUT_STA_ACC0);
createObject(OBJ_BUTTON, BUT_STA_ACC1);
createObject(OBJ_BUTTON, BUT_STA_ACC2);
createObject(OBJ_BUTTON, BUT_STA_ACC3);
break;
}
newEvent(OBJ_WIN, WIN_STA_PLAY, EVNT_DRAW);
break;
case WIN_STA_STARS:
createObject(OBJ_WIN, WIN_STA_STARS);
if (staCurrTime > 0) {
staStars++;
createObject(OBJ_FNC, FNC_STA_STAR1);
if (staCurrTime > 10) { // time remaining
staStars++;
createObject(OBJ_FNC, FNC_STA_STAR2);
createObject(OBJ_LABEL, LBL_STA_EXCEL);
}
else {
createObject(OBJ_LABEL, LBL_STA_GREAT);
}
updateStationStars();
}
else {
createObject(OBJ_ICON, ICON_STA_TIMEOUT);
createObject(OBJ_LABEL, LBL_STA_TIMEOUT);
}
newEvent(OBJ_WIN, WIN_STA_STARS, EVNT_DRAW);
break;
case WIN_STA_EDIT:
snprintf(staStartTimeBuf, IP_LNG + 1, "%d", staStartTime);
snprintf(staStatNumBuf, IP_LNG + 1, "%d", staMaxStations);
snprintf(staTurnNumBuf, IP_LNG + 1, "%d", staMaxTurnout);
snprintf(staTurnout1Buf, ADDR_LNG + 1, "%d", staTurnoutAdr1);
snprintf(staTurnout2Buf, ADDR_LNG + 1, "%d", staTurnoutAdr2);
snprintf(staTurnout3Buf, ADDR_LNG + 1, "%d", staTurnoutAdr3);
snprintf(staTurnout4Buf, ADDR_LNG + 1, "%d", staTurnoutAdr4);
for (n = 0; n < 8; n++)
switchData[SW_STA_OR1 + n].state = bitRead(staTurnoutDef, n);
createObject(OBJ_WIN, WIN_STA_EDIT);
createObject(OBJ_LABEL, LBL_STA_STATIONS);
createObject(OBJ_LABEL, LBL_STA_TURNOUTS);
createObject(OBJ_LABEL, LBL_STA_TIME);
createObject(OBJ_LABEL, LBL_STA_DESC);
createObject(OBJ_TXT, TXT_STA_STARTTIME);
createObject(OBJ_TXT, TXT_STA_STATNUM);
createObject(OBJ_TXT, TXT_STA_TURNNUM);
createObject(OBJ_TXT, TXT_STA_TURNOUT1);
createObject(OBJ_TXT, TXT_STA_TURNOUT2);
createObject(OBJ_TXT, TXT_STA_TURNOUT3);
createObject(OBJ_TXT, TXT_STA_TURNOUT4);
createObject(OBJ_BUTTON, BUT_STA_EDIT);
createObject(OBJ_SWITCH, SW_STA_OR1);
createObject(OBJ_SWITCH, SW_STA_OR2);
createObject(OBJ_SWITCH, SW_STA_OR3);
createObject(OBJ_SWITCH, SW_STA_OR4);
createObject(OBJ_SWITCH, SW_STA_INV1);
createObject(OBJ_SWITCH, SW_STA_INV2);
createObject(OBJ_SWITCH, SW_STA_INV3);
createObject(OBJ_SWITCH, SW_STA_INV4);
createObject(OBJ_BUTTON, BUT_STA_STAM);
createObject(OBJ_BUTTON, BUT_STA_STAP);
createObject(OBJ_BUTTON, BUT_STA_TURNM);
createObject(OBJ_BUTTON, BUT_STA_TURNP);
newEvent(OBJ_WIN, WIN_STA_EDIT, EVNT_DRAW);
break;
case WIN_STA_KEYB:
createObject(OBJ_WIN, WIN_STA_KEYB);
createObject(OBJ_KEYBOARD, KEYB_STA);
newEvent(OBJ_WIN, WIN_STA_KEYB, EVNT_DRAW);
break;
}
}
void alertWindow(byte err) {
errType = err;
createObject(OBJ_WIN, WIN_ALERT);
switch (err) {
case ERR_SERV:
createObject(OBJ_ICON, ICON_WARNING);
createObject(OBJ_ICON, ICON_WARNING_ON);
createObject(OBJ_LABEL, LBL_SERVICE);
break;
case ERR_CHG_WIFI:
createObject(OBJ_ICON, ICON_INFO);
createObject(OBJ_LABEL, LBL_CHG_WIFI);
break;
case ERR_FULL:
createObject(OBJ_ICON, ICON_WARNING);
createObject(OBJ_ICON, ICON_WARNING_ON);
createObject(OBJ_LABEL, LBL_STACK_FULL);
break;
case ERR_STOP:
createObject(OBJ_ICON, ICON_ESTOP);
createObject(OBJ_LABEL, LBL_ESTOP);
break;
case ERR_WAIT:
case ERR_CV:
barData[BAR_WAIT].value = 0;
setTimer(TMR_WAIT, 5, TMR_ONESHOT);
if (err == ERR_WAIT)
createObject(OBJ_ICON, ICON_WAIT);
else
createObject(OBJ_ICON, ICON_WAIT_CV);
createObject(OBJ_BAR, BAR_WAIT);
break;
case ERR_ASK_SURE:
createObject(OBJ_ICON, ICON_WARNING);
createObject(OBJ_ICON, ICON_WARNING_ON);
createObject(OBJ_LABEL, LBL_ASK_SURE);
createObject(OBJ_BUTTON, BUT_SURE_OK);
createObject(OBJ_BUTTON, BUT_SURE_CNCL);
break;
}
newEvent(OBJ_WIN, WIN_ALERT, EVNT_DRAW);
}

View File

@@ -0,0 +1,648 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
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.
*/
////////////////////////////////////////////////////////////
// ***** XPRESSNET LAN SOPORTE *****
////////////////////////////////////////////////////////////
void showErrorXnet() { // muestra pantalla de error
if (csStatus & csEmergencyOff) {
iconData[ICON_POWER].color = COLOR_RED;
setTimer (TMR_POWER, 5, TMR_PERIODIC); // Flash power icon
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
newEvent(OBJ_ICON, ICON_POWER, EVNT_DRAW);
if (isWindow(WIN_STA_PLAY)) {
fncData[FNC_STA_RAYO].state = true;
newEvent(OBJ_FNC, FNC_STA_RAYO, EVNT_DRAW);
}
}
if (csStatus & csServiceMode) {
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
alertWindow(ERR_SERV);
}
if (csStatus & csEmergencyStop) {
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
alertWindow(ERR_STOP);
}
}
void showNormalOpsXnet() {
stopTimer (TMR_POWER);
iconData[ICON_POWER].color = COLOR_GREEN;
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
newEvent(OBJ_ICON, ICON_POWER, EVNT_DRAW);
if (isWindow(WIN_STA_PLAY)) {
fncData[FNC_STA_RAYO].state = false;
newEvent(OBJ_FNC, FNC_STA_RAYO, EVNT_DRAW);
}
if (isWindow(WIN_ALERT)) {
switch (errType) {
case ERR_SERV:
case ERR_STOP:
case ERR_CV:
closeWindow(WIN_ALERT);
break;
}
}
}
uint16_t addrXnet(uint16_t adr) {
if (adr > 99) // Comprueba si es direccion larga
adr |= 0xC000;
return adr;
}
bool isRecentMM () { // Comprueba central Multimaus reciente
if ((xnetCS == 0x10) && (highVerMM > 0) && (lowVerMM > 0x02))
return true;
else
return false;
}
////////////////////////////////////////////////////////////
// ***** XPRESSNET LAN MESSAGES *****
////////////////////////////////////////////////////////////
void getStatusXnet () {
headerXN (0x21); // Command station status request (0x21,0x24,0x05)
dataXN (0x24);
sendXN();
}
void getVersionXnet () {
headerXN (0x21); // Command station software version (0x21,0x21,0x00)
dataXN (0x21);
sendXN();
}
void versionMultimaus() {
headerXN (0xF1); // Multimaus software version (0xF1,0x0A,XOR)
dataXN (0x0A);
sendXN();
}
void getResultsXnet() {
headerXN (0x21); // Request for Service Mode results (0x21,0x10,0x31)
dataXN (0x10);
sendXN();
//getResultsSM = false;
}
void resumeOperationsXnet () {
headerXN (0x21); // Resume operations request (0x21,0x81,0xA0)
dataXN (0x81);
sendXN();
}
void emergencyOffXnet() {
headerXN (0x21); // Stop operations request (emergency off)(0x21,0x80,0xA1)
dataXN (0x80);
sendXN();
}
void infoLocomotoraXnet (unsigned int loco) { // Locomotive information request (0xE3,0x00,ADRH,ADRL,XOR)
uint16_t adr;
adr = addrXnet(loco);
headerXN (0xE3);
dataXN (0x00);
dataXN (highByte(adr));
dataXN (lowByte (adr));
sendXN();
if ((xnetVersion > 0x35) || (xnetCS == 0x10)) {
headerXN (0xE3);
if (xnetCS == 0x10)
dataXN (0xF0); // Locomotive function F13..F20 info MM (0xE3,0xF0,ADRH,ADRL,XOR)
else
dataXN (0x09); // Locomotive function F13..F28 info v3.6 (0xE3,0x09,ADRH,ADRL,XOR)
dataXN (highByte(adr));
dataXN (lowByte (adr));
sendXN();
}
getInfoLoco = false;
}
void locoOperationSpeedXnet() { // Locomotive speed and direction operations (0xE4,ID,ADRH,ADRL,SPD,XOR)
uint16_t adr;
adr = addrXnet(locoData[myLocoData].myAddr.address);
headerXN (0xE4);
if (bitRead(locoData[myLocoData].mySteps, 2)) { // 128 steps
dataXN (0x13);
}
else {
if (bitRead(locoData[myLocoData].mySteps, 1)) { // 28 steps
dataXN (0x12);
}
else {
dataXN (0x10); // 14 steps
}
}
dataXN (highByte(adr));
dataXN (lowByte(adr));
dataXN (locoData[myLocoData].mySpeed | locoData[myLocoData].myDir);
sendXN();
bitClear(locoData[myLocoData].mySteps, 3); // currently operated by me
updateSpeedDir();
}
void funcOperationsXnet (byte fnc) { // Function operation instructions (0xE4,ID,ADRH,ADRL,GRP,XOR)
byte grp, grpID;
uint16_t adr;
adr = addrXnet(locoData[myLocoData].myAddr.address);
if (fnc > 20) {
grpID = 0x28; // F21..F28
grp = ((locoData[myLocoData].myFunc.xFunc[2] >> 5) & 0x07);
grp |= (locoData[myLocoData].myFunc.xFunc[3] << 3);
}
else {
if (fnc > 12) {
if (xnetCS == 0x10)
grpID = 0xF3; // F13..F20 MM (0xE4,0xF3,ADH,ADL,F13F20,XOR)
else
grpID = 0x23; // F13..F20
grp = ((locoData[myLocoData].myFunc.xFunc[1] >> 5) & 0x07);
grp |= (locoData[myLocoData].myFunc.xFunc[2] << 3);
}
else {
if (fnc > 8) {
grpID = 0x22; // F9..F12
grp = ((locoData[myLocoData].myFunc.xFunc[1] >> 1) & 0x0F);
}
else {
if (fnc > 4) {
grpID = 0x21; // F5..F8
grp = ((locoData[myLocoData].myFunc.xFunc[0] >> 5) & 0x07);
if (bitRead(locoData[myLocoData].myFunc.xFunc[1], 0))
grp |= 0x08;
}
else {
grpID = 0x20; // F0..F4
grp = ((locoData[myLocoData].myFunc.xFunc[0] >> 1) & 0x0F);
if (bitRead(locoData[myLocoData].myFunc.xFunc[0], 0))
grp |= 0x10;
}
}
}
}
headerXN (0xE4);
dataXN (grpID);
dataXN (highByte(adr));
dataXN (lowByte(adr));
dataXN (grp);
sendXN();
bitClear(locoData[myLocoData].mySteps, 3); // currently operated by me
}
byte getCurrentStepXnet() {
byte currStep;
if (bitRead(locoData[myLocoData].mySteps, 2)) { // 128 steps -> 0..126
if (locoData[myLocoData].mySpeed > 1)
return (locoData[myLocoData].mySpeed - 1);
}
else {
if (bitRead(locoData[myLocoData].mySteps, 1)) { // 28 steps -> 0..28 '---04321' -> '---43210'
currStep = (locoData[myLocoData].mySpeed << 1) & 0x1F;
bitWrite(currStep, 0, bitRead(locoData[myLocoData].mySpeed, 4));
if (currStep > 3)
return (currStep - 3);
}
else { // 14 steps -> 0..14
if (locoData[myLocoData].mySpeed > 1)
return (locoData[myLocoData].mySpeed - 1);
}
}
return (0);
}
void setAccessoryXnet (unsigned int direccion, bool activa, byte posicion) { // 1..1024
byte adr, dato;
direccion--; // 000000AAAAAAAABB
adr = (direccion >> 2) & 0x00FF; // AAAAAAAA
dato = ((direccion & 0x0003) << 1) | 0x80; // 1000xBBx
if (posicion > 0)
dato |= 0x01;
if (activa) { // 1000dBBD
dato |= 0x08;
}
headerXN (0x52); // Accessory Decoder operation request (0x52,AAAAAAAA,1000dBBD,XOR)
dataXN (adr);
dataXN (dato);
sendXN();
}
void setTimeXnet(byte hh, byte mm, byte rate) {
clockHour = hh;
clockMin = mm;
clockRate = rate;
if (rate > 0) {
headerXN (0x24); // set clock
dataXN (0x2B);
dataXN (hh);
dataXN (mm);
dataXN (rate);
sendXN ();
/*
headerXN (0x21); // start clock
dataXN (0x2C);
sendXN (0x07);
*/
}
else {
headerXN (0x21); // recommended for rate=0. stop clock
dataXN (0x2D);
sendXN ();
}
}
void readCVXnet (unsigned int adr, byte stepPrg) {
if (!modeProg) { // Read only in Direct mode
if (isRecentMM()) {
headerXN (0x23); // Multimaus v1.03
dataXN (0x15);
adr--;
dataXN (highByte(adr) & 0x03);
dataXN (lowByte(adr));
sendXN();
lastCV = lowByte(adr) + 1;
}
else {
headerXN (0x22);
if (xnetVersion > 0x35)
dataXN (0x18 | (highByte(adr) & 0x03)); // v3.6 & up CV1..CV1024
else
dataXN (0x15); // v3.0 CV1..CV256
dataXN (lowByte(adr));
sendXN();
lastCV = lowByte(adr);
}
getResultsSM = true;
infoTimer = millis();
progStepCV = stepPrg;
//DEBUG_MSG("Read CV %d", adr);
}
}
void writeCVXnet (unsigned int adr, unsigned int data, byte stepPrg) {
uint16_t adrLoco;
if (modeProg) {
headerXN (0xE6); // Operations Mode Programming byte mode write request (0xE6,0x30,ADRH,ADRL,0xEC+C,CV,DATA,XOR)
dataXN (0x30);
adrLoco = addrXnet(locoData[myLocoData].myAddr.address);
dataXN (highByte(adrLoco));
dataXN (lowByte(adrLoco));
adr--;
dataXN (0xEC | (highByte(adr) & 0x03));
dataXN (lowByte(adr));
dataXN(data);
sendXN();
}
else {
if (isRecentMM()) {
headerXN (0x24); // Multimaus v1.03
dataXN (0x16);
adr--;
dataXN (highByte(adr) & 0x03);
dataXN (lowByte(adr));
dataXN(data);
sendXN();
lastCV = lowByte(adr) + 1;
}
else {
headerXN (0x23);
if (xnetVersion > 0x35)
dataXN (0x1C | (highByte(adr) & 0x03)); // v3.6 & up CV1..CV1024
else
dataXN (0x16); // v3.0 CV1..CV256
dataXN (lowByte(adr));
dataXN(data);
sendXN();
lastCV = lowByte(adr);
}
getResultsSM = true;
infoTimer = millis();
}
progStepCV = stepPrg;
//DEBUG_MSG("Write CV%d = %d", adr, data);
}
////////////////////////////////////////////////////////////
// ***** XPRESSNET LAN DECODE *****
////////////////////////////////////////////////////////////
void headerXN (byte header) {
txBytes = HEADER; // coloca header en el buffer
txXOR = header;
txBuffer[txBytes++] = header;
txBuffer[FRAME1] = 0xFF;
txBuffer[FRAME2] = 0xFE;
}
void dataXN (byte dato) {
txBuffer[txBytes++] = dato; // coloca dato en el buffer
txXOR ^= dato;
}
void sendXN () {
bool recvAnswer;
txBuffer[txBytes++] = txXOR; // coloca XOR byte en el buffer
#ifdef DEBUG
Serial.print(F("TX: "));
for (uint8_t x = 0; x < txBytes; x++) {
uint8_t val = txBuffer[x];
if (val < 16)
Serial.print('0');
Serial.print(val, HEX);
Serial.print(' ');
}
Serial.println();
#endif
Client.write((byte *)&txBuffer[FRAME1], txBytes); // envia paquete xpressnet
timeoutXnet = millis();
recvAnswer = false;
while ((millis() - timeoutXnet < 500) && (!recvAnswer)) // wait answer for 500ms
recvAnswer = xnetReceive();
}
bool xnetReceive() {
bool getAnswer;
getAnswer = false;
while (Client.available()) {
rxData = Client.read();
//DEBUG_MSG("%d-%02X", rxIndice, rxData);
switch (rxIndice) {
case FRAME1:
rxBufferXN[FRAME1] = rxData;
if (rxData == 0xFF) // 0xFF... Posible inicio de paquete
rxIndice = FRAME2;
break;
case FRAME2:
rxBufferXN[FRAME2] = rxData;
switch (rxData) {
case 0xFF: // 0xFF 0xFF... FRAME2 puede ser FRAME1 (inicio de otro paquete)
break;
case 0xFE: // 0xFF 0xFE... Inicio paquete correcto
case 0xFD: // 0xFF 0xFD... Inicio paquete de broadcast correcto
rxIndice = HEADER;
rxXOR = 0;
break;
default: // 0xFF 0xXX... No es inicio de paquete
rxIndice = FRAME1;
break;
}
break;
default:
rxBufferXN[rxIndice++] = rxData;
rxXOR ^= rxData;
if (((rxBufferXN[HEADER] & 0x0F) + 4) == rxIndice) { // si se han recibido todos los datos indicados en el paquete
if (rxXOR == 0) { // si el paquete es correcto
rxBytes = rxIndice;
#ifdef DEBUG
Serial.print(F("RX: "));
for (uint8_t x = 0; x < rxBytes; x++) {
uint8_t val = rxBufferXN[x];
if (val < 16)
Serial.print('0');
Serial.print(val, HEX);
Serial.print(' ');
}
Serial.println();
#endif
procesaXN(); // nuevo paquete recibido, procesarlo
getAnswer = true;
}
rxIndice = FRAME1; // proximo paquete
}
break;
}
}
return getAnswer;
}
void processXnet () { // procesa Xpressnet
xnetReceive();
if (getInfoLoco && (csStatus == csNormalOps))
infoLocomotoraXnet(addrXnet(locoData[myLocoData].myAddr.address));
if (millis() - infoTimer > 1000UL) { // Cada segundo
infoTimer = millis();
if (getResultsSM) // Resultados de CV pendientes
getResultsXnet(); // pide resultados
else {
if (bitRead(locoData[myLocoData].mySteps, 3)) // Loco controlada por otro mando
getInfoLoco = true; // pide info locomotora
if (askMultimaus) { // pide info Multimaus
askMultimaus = false;
versionMultimaus();
}
}
}
if (progFinished) { // fin de lectura/programacion CV
progFinished = false;
endProg();
}
if (millis() - pingTimer > XNET_PING_INTERVAL) { // Refresca para mantener la conexion
pingTimer = millis();
getStatusXnet(); // pide estado de la central
}
}
void procesaXN () {
byte n, longitud, modulo, dato;
uint16_t adr;
switch (rxBufferXN[HEADER]) { // segun el header byte
case 0x61:
switch (rxBufferXN[DATA1]) {
case 0x01: // Normal operation resumed (0x61,0x01,0x60)
csStatus = csNormalOps;
showNormalOpsXnet();
break;
case 0x08: // Z21 LAN_X_BC_TRACK_SHORT_CIRCUIT (0x61,0x08,XOR)
case 0x00: // Track power off (0x61,0x00,0x61)
csStatus |= csEmergencyOff;
showErrorXnet();
break;
case 0x02: // Service mode entry (0x61,0x02,0x63)
csStatus |= csServiceMode;
if (!getResultsSM) // show 'Service Mode' if we aren't programming CV
showErrorXnet();
break;
case 0x12: // Programming info. "shortcircuit" (0x61,0x12,XOR)
case 0x13: // Programming info. "Data byte not found" (0x61,0x13,XOR)
CVdata = 0x0600;
getResultsSM = false;
progFinished = true;
break;
case 0x81: // Command station busy response (0x61,0x81,XOR)
break;
case 0x1F: // Programming info. "Command station busy" (0x61,0x1F,XOR)
getResultsSM = true;
infoTimer = millis();
break;
case 0x82: // Instruction not supported by command station (0x61,0x82,XOR)
getResultsSM = false;
if (csStatus & csServiceMode) {
CVdata = 0x0600;
progFinished = true;
}
break;
}
break;
case 0x81:
if (rxBufferXN[DATA1] == 0) { // Emergency Stop (0x81,0x00,0x81)
csStatus |= csEmergencyStop;
showErrorXnet();
}
break;
case 0x62:
if (rxBufferXN[DATA1] == 0x22) { // Command station status indication response (0x62,0x22,DATA,XOR)
csStatus = rxBufferXN[DATA2] & (csEmergencyStop | csEmergencyOff | csServiceMode) ;
if ((xnetCS >= 0x10) && (rxBufferXN[DATA2] & csProgrammingModeActive)) // Multimaus/Z21 Service Mode
csStatus |= csServiceMode;
if (csStatus == csNormalOps)
showNormalOpsXnet();
else
showErrorXnet();
}
break;
case 0x63:
switch (rxBufferXN[DATA1]) {
case 0x03: // Broadcast "Modellzeit" (0x63,0x03,dddhhhhh,s0mmmmmm,XOR) (v4.0)
clockHour = rxBufferXN[DATA2] & 0x1F;
clockMin = rxBufferXN[DATA3] & 0x3F;
clockRate = !bitRead(rxBufferXN[DATA3], 7);
updateFastClock();
break;
case 0x14: // Service Mode response for Direct CV mode (0x63,0x1x,CV,DATA,XOR)
case 0x15:
case 0x16:
case 0x17:
if (rxBufferXN[DATA2] == lastCV) { // comprobar CV (DR5000)
lastCV ^= 0x55;
getResultsSM = false;
CVdata = rxBufferXN[DATA3];
progFinished = true;
}
break;
case 0x21: // Command station software version (0x63,0x21,VER,ID,XOR)
xnetVersion = rxBufferXN[DATA2];
xnetCS = rxBufferXN[DATA3];
if (xnetCS == 0x10)
askMultimaus = true;
break;
}
break;
case 0xE3:
if (rxBufferXN[DATA1] == 0x40) { // Locomotive is being operated by another device response (0xE3,0x40,ADRH,ADRL,XOR)
adr = addrXnet(locoData[myLocoData].myAddr.address);
if ((rxBufferXN[DATA3] == lowByte(adr)) && (rxBufferXN[DATA2] == highByte(adr))) { // DR5000 workaround
bitSet(locoData[myLocoData].mySteps, 3);
}
}
if (rxBufferXN[DATA1] == 0x52) { // Locomotive function info F13..F28 (0xE3,0x52,FNC,FNC,XOR)
locoData[myLocoData].myFunc.Bits &= 0xE0001FFF;
locoData[myLocoData].myFunc.Bits |= ((unsigned long)rxBufferXN[DATA2] << 13);
locoData[myLocoData].myFunc.Bits |= ((unsigned long)rxBufferXN[DATA3] << 21);
updateFuncState(isWindow(WIN_THROTTLE));
}
break;
case 0xE4:
if ((rxBufferXN[DATA1] & 0xF0) == 0x00) { // Locomotive information normal locomotive (0xE4,ID,SPD,FKTA,FKTB,XOR)
locoData[myLocoData].mySteps = rxBufferXN[DATA1]; // '0000BFFF'
locoData[myLocoData].myDir = rxBufferXN[DATA2] & 0x80; // 'RVVVVVVV'
locoData[myLocoData].mySpeed = rxBufferXN[DATA2] & 0x7F;
locoData[myLocoData].myFunc.Bits &= 0xFFFFE000; // '000FFFFF','FFFFFFFF'
locoData[myLocoData].myFunc.Bits |= ((unsigned long)rxBufferXN[DATA4] << 5);
locoData[myLocoData].myFunc.xFunc[0] |= ((rxBufferXN[DATA3] & 0x0F) << 1);
bitWrite(locoData[myLocoData].myFunc.xFunc[0], 0, bitRead(rxBufferXN[DATA3], 4));
updateFuncState(isWindow(WIN_THROTTLE));
if (isWindow(WIN_THROTTLE) || isWindow(WIN_SPEEDO))
updateSpeedHID();
}
break;
case 0xE7:
if ((rxBufferXN[DATA1] & 0xF0) == 0x00) { // Locomotive function info F13..F20 MM (0xE7,STP,SPD,FNC,FNC,FNC,0x00,0x00,XOR)
locoData[myLocoData].mySteps = rxBufferXN[DATA1]; // '0000BFFF'
locoData[myLocoData].myDir = rxBufferXN[DATA2] & 0x80; // 'RVVVVVVV'
locoData[myLocoData].mySpeed = rxBufferXN[DATA2] & 0x7F;
locoData[myLocoData].myFunc.Bits &= 0xFE00000;
locoData[myLocoData].myFunc.Bits |= ((unsigned long)rxBufferXN[DATA5] << 13);
locoData[myLocoData].myFunc.Bits |= ((unsigned long)rxBufferXN[DATA4] << 5);
locoData[myLocoData].myFunc.xFunc[0] |= ((rxBufferXN[DATA3] & 0x0F) << 1);
bitWrite(locoData[myLocoData].myFunc.xFunc[0], 0, bitRead(rxBufferXN[DATA3], 4));
updateFuncState(isWindow(WIN_THROTTLE));
if (isWindow(WIN_THROTTLE) || isWindow(WIN_SPEEDO))
updateSpeedHID();
}
break;
case 0xF3:
if (rxBufferXN[DATA1] == 0x0A) { // Multimaus firmware version (0xF3,0x0A,VERH,VERL,XOR)
highVerMM = rxBufferXN[DATA2];
lowVerMM = rxBufferXN[DATA3];
}
break;
default:
if ((rxBufferXN[HEADER] & 0xF0) == 0x40) { // Feedback broadcast / Accessory decoder information response (0x4X,MOD,DATA,...,XOR)
/*
for (n = HEADER; n < (rxBytes - 2); n += 2) {
modulo = rxBufferXN[n + 1];
dato = rxBufferXN[n + 2];
if (modulo == miModulo) { // Si es mi desvio guarda su posicion
if (bitRead(dato, 4) == bitRead(miAccPos, 1)) {
if (bitRead(miAccPos, 0))
myPosTurnout = (dato >> 2) & 0x03;
else
myPosTurnout = dato & 0x03;
if (scrOLED == SCR_TURNOUT)
updateOLED = true;
}
}
#ifdef USE_AUTOMATION
for (byte n = 0; n < MAX_AUTO_SEQ; n++) {
if ((automation[n].opcode & OPC_AUTO_MASK) == OPC_AUTO_FBK) {
if (modulo == automation[n].param) {
unsigned int nibble = (dato & 0x10) ? 0x0F : 0xF0;
automation[n].value &= nibble;
nibble = (dato & 0x10) ? (dato << 4) : (dato & 0x0F);
automation[n].value |= nibble;
}
}
}
#endif
modulo++;
if (modulo == Shuttle.moduleA) // shuttle contacts
updateShuttleStatus(&Shuttle.statusA, dato);
if (modulo == Shuttle.moduleB)
updateShuttleStatus(&Shuttle.statusB, dato);
}
*/
}
break;
}
}

View File

@@ -0,0 +1,581 @@
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
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.
*/
////////////////////////////////////////////////////////////
// ***** Z21 SOPORTE *****
////////////////////////////////////////////////////////////
void readCVZ21 (unsigned int adr, byte stepPrg) {
if (!modeProg) {
askZ21begin (LAN_X_Header);
askZ21data (0x23);
askZ21data (0x11);
adr--;
askZ21data ((adr >> 8) & 0xFF);
askZ21data (adr & 0xFF);
askZ21xor ();
sendUDP (0x09);
waitResultCV = true;
lastCV = lowByte(adr);
progStepCV = stepPrg;
DEBUG_MSG("Read CV %d", adr + 1);
}
}
void writeCVZ21 (unsigned int adr, unsigned int data, byte stepPrg) {
byte Adr_MSB;
if (modeProg) {
askZ21begin (LAN_X_Header);
askZ21data (0xE6);
askZ21data (0x30);
Adr_MSB = locoData[myLocoData].myAddr.adr[1] & 0x3F;
if (locoData[myLocoData].myAddr.address & 0x3F80)
Adr_MSB |= 0xC0;
askZ21data (Adr_MSB);
askZ21data (locoData[myLocoData].myAddr.adr[0]);
adr--;
askZ21data (0xEC | ((adr >> 8) & 0x03));
askZ21data (adr & 0xFF);
askZ21data (data);
askZ21xor ();
sendUDP (0x0C);
}
else {
askZ21begin (LAN_X_Header);
askZ21data (0x24);
askZ21data (0x12);
adr--;
askZ21data ((adr >> 8) & 0xFF);
askZ21data (adr & 0xFF);
askZ21data (data);
askZ21xor ();
sendUDP (0x0A);
waitResultCV = true;
lastCV = lowByte(adr);
}
progStepCV = stepPrg;
DEBUG_MSG("Write CV%d = %d", adr + 1, data);
}
void showErrorZ21() { // muestra pantalla de error
if (csStatus & csTrackVoltageOff) {
iconData[ICON_POWER].color = COLOR_RED;
setTimer (TMR_POWER, 5, TMR_PERIODIC); // Flash power icon
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
newEvent(OBJ_ICON, ICON_POWER, EVNT_DRAW);
if (isWindow(WIN_STA_PLAY)) {
fncData[FNC_STA_RAYO].state = true;
newEvent(OBJ_FNC, FNC_STA_RAYO, EVNT_DRAW);
}
}
if (csStatus & csProgrammingModeActive) {
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
alertWindow(ERR_SERV);
}
if (csStatus & csEmergencyStop) {
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
alertWindow(ERR_STOP);
}
}
void showNormalOpsZ21() {
stopTimer (TMR_POWER);
iconData[ICON_POWER].color = COLOR_GREEN;
if ((isWindow(WIN_THROTTLE)) || (isWindow(WIN_STEAM)))
newEvent(OBJ_ICON, ICON_POWER, EVNT_DRAW);
if (isWindow(WIN_STA_PLAY)) {
fncData[FNC_STA_RAYO].state = false;
newEvent(OBJ_FNC, FNC_STA_RAYO, EVNT_DRAW);
}
if (isWindow(WIN_ALERT)) {
switch (errType) {
case ERR_SERV:
case ERR_STOP:
case ERR_CV:
closeWindow(WIN_ALERT);
break;
}
}
}
void setTimeZ21(byte hh, byte mm, byte rate) {
clockHour = hh;
clockMin = mm;
clockRate = rate;
askZ21begin (LAN_FAST_CLOCK_CONTROL); // set clock
if (rate > 0) {
askZ21data (0x24);
askZ21data (0x2B);
askZ21data (hh);
askZ21data (mm);
askZ21data (rate);
askZ21xor ();
sendUDP (0x0A);
askZ21begin (LAN_FAST_CLOCK_CONTROL);
askZ21data (0x21); // start clock
askZ21data (0x2C);
askZ21xor ();
sendUDP (0x07);
}
else {
askZ21data (0x21); // recommended for rate=0. stop clock
askZ21data (0x2D);
askZ21xor ();
sendUDP (0x07);
}
}
////////////////////////////////////////////////////////////
// ***** Z21 DECODE *****
////////////////////////////////////////////////////////////
void processZ21() {
int len, packetSize;
packetSize = Udp.parsePacket(); // z21 UDP packet
if (packetSize) {
len = Udp.read(packetBuffer, packetSize); // read the packet into packetBufffer
ReceiveZ21 (len, packetBuffer); // decode received packet
}
delay(0);
if (millis() - infoTimer > 1000UL) { // Cada segundo
infoTimer = millis();
pingTimer++;
if (pingTimer >= Z21_PING_INTERVAL) {
pingTimer = 0;
if (!(csStatus & csProgrammingModeActive))
getStatusZ21();
}
/*
battery = ESP.getVcc (); // Read VCC voltage
if (battery < LowBattADC)
lowBATT = true;
*/
}
if (progFinished) { // fin de lectura/programacion CV
progFinished = false;
endProg();
}
delay(0);
}
// -------------------------------------------------------------------------------------
void setBroadcastFlags (unsigned long bFlags) {
askZ21begin (LAN_SET_BROADCASTFLAGS);
askZ21data (bFlags & 0xFF);
askZ21data ((bFlags >> 8) & 0xFF);
askZ21data ((bFlags >> 16) & 0xFF);
askZ21data ((bFlags >> 24) & 0xFF);
sendUDP (0x08);
}
void resumeOperationsZ21 () { // LAN_X_SET_TRACK_POWER_ON
askZ21begin (LAN_X_Header);
askZ21data (0x21);
askZ21data (0x81);
askZ21xor ();
sendUDP (0x07);
}
void emergencyOffZ21() { // LAN_X_SET_TRACK_POWER_OFF
askZ21begin (LAN_X_Header);
askZ21data (0x21);
askZ21data (0x80);
askZ21xor ();
sendUDP (0x07);
}
void getStatusZ21 () {
askZ21begin (LAN_X_Header);
askZ21data (0x21);
askZ21data (0x24);
askZ21xor ();
sendUDP (0x07);
}
void getSerialNumber () {
askZ21begin (LAN_GET_SERIAL_NUMBER);
sendUDP (0x04);
}
void infoLocomotoraZ21 (unsigned int Adr) {
byte Adr_MSB;
askZ21begin (LAN_X_Header);
askZ21data (0xE3);
askZ21data (0xF0);
Adr_MSB = (Adr >> 8) & 0x3F;
if (Adr & 0x3F80)
Adr_MSB |= 0xC0;
askZ21data (Adr_MSB);
askZ21data (Adr & 0xFF);
askZ21xor ();
sendUDP (0x09);
}
void locoOperationSpeedZ21() {
byte Adr_MSB;
DEBUG_MSG("Loco Operations")
askZ21begin (LAN_X_Header);
askZ21data (0xE4);
if (bitRead(locoData[myLocoData].mySteps, 2)) { // 128 steps
askZ21data (0x13);
}
else {
if (bitRead(locoData[myLocoData].mySteps, 1)) { // 28 steps
askZ21data (0x12);
}
else {
askZ21data (0x10); // 14 steps
}
}
Adr_MSB = locoData[myLocoData].myAddr.adr[1] & 0x3F;
if (locoData[myLocoData].myAddr.address & 0x3F80)
Adr_MSB |= 0xC0;
askZ21data (Adr_MSB);
askZ21data (locoData[myLocoData].myAddr.adr[0]);
askZ21data (locoData[myLocoData].mySpeed | locoData[myLocoData].myDir);
askZ21xor ();
sendUDP (0x0A);
bitClear(locoData[myLocoData].mySteps, 3); // currently operated by me
updateSpeedDir();
}
byte getCurrentStepZ21() {
byte currStep;
DEBUG_MSG("Get Steps: %02X - Speed: %02X", locoData[myLocoData].mySteps, locoData[myLocoData].mySpeed);
if (bitRead(locoData[myLocoData].mySteps, 2)) { // 128 steps -> 0..126
if (locoData[myLocoData].mySpeed > 1)
return (locoData[myLocoData].mySpeed - 1);
}
else {
if (bitRead(locoData[myLocoData].mySteps, 1)) { // 28 steps -> 0..28 '---04321' -> '---43210'
currStep = (locoData[myLocoData].mySpeed << 1) & 0x1F;
bitWrite(currStep, 0, bitRead(locoData[myLocoData].mySpeed, 4));
if (currStep > 3)
return (currStep - 3);
}
else { // 14 steps -> 0..14
if (locoData[myLocoData].mySpeed > 1)
return (locoData[myLocoData].mySpeed - 1);
}
}
return (0);
}
void funcOperationsZ21 (byte fnc) {
byte Adr_MSB;
askZ21begin (LAN_X_Header);
askZ21data (0xE4);
askZ21data (0xF8);
Adr_MSB = locoData[myLocoData].myAddr.adr[1] & 0x3F;
if (locoData[myLocoData].myAddr.address & 0x3F80)
Adr_MSB |= 0xC0;
askZ21data (Adr_MSB);
askZ21data (locoData[myLocoData].myAddr.adr[0]);
if (bitRead(locoData[myLocoData].myFunc.Bits, fnc))
askZ21data (fnc | 0x40);
else
askZ21data (fnc);
askZ21xor ();
sendUDP (0x0A);
bitClear(locoData[myLocoData].mySteps, 3); // currently operated by me
}
void infoDesvio (unsigned int FAdr) {
FAdr--;
askZ21begin (LAN_X_Header);
askZ21data (0x43);
askZ21data ((FAdr >> 8) & 0xFF);
askZ21data (FAdr & 0xFF);
askZ21xor ();
sendUDP (0x08);
}
void setAccessoryZ21 (unsigned int FAdr, int pair, bool active) {
byte db2;
FAdr--;
askZ21begin (LAN_X_Header);
askZ21data (0x53);
askZ21data ((FAdr >> 8) & 0xFF);
askZ21data (FAdr & 0xFF);
db2 = active ? 0x88 : 0x80;
if (pair > 0)
db2 |= 0x01;
askZ21data (db2); // '10Q0A00P'
askZ21xor ();
sendUDP (0x09);
}
void getFeedbackInfo (byte group) {
askZ21begin (LAN_RMBUS_GETDATA);
askZ21data (group);
sendUDP (0x05);
}
void ReceiveZ21 (int len, byte * packet) { // get UDP packet, maybe more than one!!
int DataLen, isPacket;
#ifdef DEBUG____X
Serial.print("\nRX Length: ");
Serial.println (len);
for (int i = 0; i < len; i++) {
Serial.print(packet[i], HEX);
Serial.print(" ");
}
Serial.println();
#endif
isPacket = 1;
while (isPacket) {
DataLen = (packet[DATA_LENH] << 8) + packet[DATA_LENL];
DecodeZ21 (DataLen, packet);
if (DataLen >= len) {
isPacket = 0;
}
else {
packet = packet + DataLen;
len = len - DataLen;
}
delay(0);
}
}
void DecodeZ21 (int len, byte * packet) { // decode z21 UDP packets
int Header, DataLen;
unsigned int FAdr;
byte group;
Header = (packet[DATA_HEADERH] << 8) + packet[DATA_HEADERL];
switch (Header) {
case LAN_GET_SERIAL_NUMBER:
break;
case LAN_GET_CODE: // FW 1.28
break;
case LAN_GET_HWINFO:
break;
case LAN_GET_BROADCASTFLAGS:
break;
case LAN_GET_LOCOMODE:
break;
case LAN_GET_TURNOUTMODE:
break;
case LAN_RMBUS_DATACHANGED:
/*
if (Shuttle.moduleA > 0) { // only check shuttle contacts
if ((packet[4] == 0x01) && (Shuttle.moduleA > 10))
Shuttle.statusA = packet[Shuttle.moduleA - 6];
if ((packet[4] == 0x00) && (Shuttle.moduleA < 11))
Shuttle.statusA = packet[Shuttle.moduleA + 4];
}
if (Shuttle.moduleB > 0) {
if ((packet[4] == 0x01) && (Shuttle.moduleB > 10))
Shuttle.statusB = packet[Shuttle.moduleB - 6];
if ((packet[4] == 0x00) && (Shuttle.moduleB < 11))
Shuttle.statusB = packet[Shuttle.moduleB + 4];
}
#ifdef USE_AUTOMATION
for (byte n = 0; n < MAX_AUTO_SEQ; n++) {
if ((automation[n].opcode & OPC_AUTO_MASK) == OPC_AUTO_FBK) {
if ((packet[4] == 0x01) && (automation[n].param > 9))
automation[n].value = packet[automation[n].param - 5];
if ((packet[4] == 0x00) && (automation[n].param < 10))
automation[n].value = packet[automation[n].param + 5];
DEBUG_MSG("RBUS %d", automation[n].value)
}
}
#endif
*/
break;
case LAN_SYSTEMSTATE_DATACHANGED:
csStatus = packet[16] & (csEmergencyStop | csTrackVoltageOff | csProgrammingModeActive); // CentralState
if (packet[16] & csShortCircuit)
csStatus |= csTrackVoltageOff;
break;
case LAN_RAILCOM_DATACHANGED:
break;
case LAN_LOCONET_Z21_TX: // a message has been written to the LocoNet bus by the Z21.
case LAN_LOCONET_Z21_RX: // a message has been received by the Z21 from the LocoNet bus.
case LAN_LOCONET_FROM_LAN: // another LAN client has written a message to the LocoNet bus via the Z21.
switch (packet[4]) {
case 0x83:
csStatus = csNormalOps; // OPC_GPON
showNormalOpsZ21();
break;
case 0x82:
csStatus |= csTrackVoltageOff; // OPC_GPOFF
showErrorZ21();
break;
}
break;
case LAN_LOCONET_DETECTOR:
break;
case LAN_FAST_CLOCK_DATA: // fast clock data FW 1.43
if (packet[8] & 0x80) { // Stop flag
clockRate = 0;
}
else {
clockHour = packet[6] & 0x1F;
clockMin = packet[7] & 0x3F;
clockRate = packet[9] & 0x3F;
updateFastClock();
}
DEBUG_MSG("Clock: %d:%d %d", clockHour, clockMin, clockRate);
break;
case LAN_X_Header:
switch (packet[XHEADER]) {
case 0x43: // LAN_X_TURNOUT_INFO
FAdr = (packet[DB0] << 8) + packet[DB1] + 1;
/*
if (FAdr == myTurnout) {
myPosTurnout = packet[DB2] & 0x03;
if (scrOLED == SCR_TURNOUT)
updateOLED = true;
}
*/
break;
case 0x61:
switch (packet[DB0]) {
case 0x01: // LAN_X_BC_TRACK_POWER_ON
csStatus = csNormalOps;
showNormalOpsZ21();
break;
case 0x08: // LAN_X_BC_TRACK_SHORT_CIRCUIT
csStatus |= csShortCircuit;
case 0x00: // LAN_X_BC_TRACK_POWER_OFF
csStatus |= csTrackVoltageOff;
showErrorZ21();
break;
case 0x02: // LAN_X_BC_PROGRAMMING_MODE
csStatus |= csProgrammingModeActive;
if (!waitResultCV)
showErrorZ21();
break;
case 0x12: // LAN_X_CV_NACK_SC
case 0x13: // LAN_X_CV_NACK
CVdata = 0x0600;
waitResultCV = false;
progFinished = true;
break;
case 0x82: // LAN_X_UNKNOWN_COMMAND
break;
}
break;
case 0x62:
switch (packet[DB0]) {
case 0x22: // LAN_X_STATUS_CHANGED
csStatus = packet[DB1] & (csEmergencyStop | csTrackVoltageOff | csProgrammingModeActive);
if (packet[DB1] & csShortCircuit)
csStatus |= csTrackVoltageOff;
if (csStatus == csNormalOps)
showNormalOpsZ21();
else
showErrorZ21();
break;
}
break;
case 0x64:
if (packet[DB0] == 0x14) { // LAN_X_CV_RESULT
if (packet[DB2] == lastCV) {
lastCV ^= 0x55;
CVdata = packet[DB3];
waitResultCV = false;
progFinished = true;
}
}
break;
case 0x81:
if (packet[DB0] == 0) { // LAN_X_BC_STOPPED
csStatus |= csEmergencyStop;
showErrorZ21();
}
break;
case 0xEF: // LAN_X_LOCO_INFO
DEBUG_MSG("RX: Loco data")
FAdr = ((packet[DB0] << 8) + packet[DB1]) & 0x3FFF;
if (FAdr == locoData[myLocoData].myAddr.address) {
locoData[myLocoData].mySteps = packet[DB2]; // '0000BFFF'
locoData[myLocoData].myDir = packet[DB3] & 0x80; // 'RVVVVVVV'
locoData[myLocoData].mySpeed = packet[DB3] & 0x7F;
locoData[myLocoData].myFunc.Bits &= 0xE0000000; // '000FFFFF','FFFFFFFF'
locoData[myLocoData].myFunc.xFunc[0] |= ((packet[DB4] & 0x0F) << 1);
bitWrite(locoData[myLocoData].myFunc.xFunc[0], 0, bitRead(packet[DB4], 4));
locoData[myLocoData].myFunc.Bits |= (unsigned long)(packet[DB5] << 5);
locoData[myLocoData].myFunc.Bits |= (unsigned long)(packet[DB6] << 13);
locoData[myLocoData].myFunc.Bits |= (unsigned long)(packet[DB7] << 21);
updateFuncState(isWindow(WIN_THROTTLE));
if (isWindow(WIN_THROTTLE) || isWindow(WIN_SPEEDO))
updateSpeedHID(); // set encoder
}
break;
}
break;
default: // Header other
break;
}
}
void askZ21begin (unsigned int header) {
OutData[DATA_HEADERL] = header & 0xFF;
OutData[DATA_HEADERH] = header >> 8;
OutPos = XHEADER;
OutXOR = 0;
}
void askZ21data (byte data) {
OutData[OutPos++] = data;
OutXOR ^= data;
}
void askZ21xor () {
OutData[OutPos] = OutXOR;
}
void sendUDP (int len) {
OutData[DATA_LENL] = len & 0xFF;
OutData[DATA_LENH] = len >> 8;
Udp.beginPacket(wifiSetting.CS_IP, z21Port);
Udp.write(OutData, len);
Udp.endPacket();
delay(0);
#ifdef DEBUG___X
Serial.print("TX: ");
for (int i = 0; i < len; i++) {
Serial.print(OutData[i], HEX);
Serial.print(" ");
}
Serial.println();
#endif
}