Debut projet Platformio
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user