/** * \file cimditprofile.h * \brief Declaration of the CimditProfile class * \author GrumpyDeveloper (Sascha Nitsch) * \copyright 2022 Sascha Nitsch * Licensed under MIT license * */ #include "defines.h" // system includes #include #include #include #ifdef HAVE_MIDI #include #endif #ifndef EXTERNAL_EEPROM #include #endif #ifdef HAVE_DISPLAY #include "cimditssd1306.h" #endif // own includes #include "cimditprofile.h" extern CimditSSD1306 display; extern CimditHAL hal; /* structure of profile in eeprom uint16_t total size (including length bytes) uint8_t number of profiles for each profile: uint16_t length length of profile in bytes (including length bytes) char profile[] 0-terminated profile name uint8_t custom image (0/1) if custom image uint8_t[512] image data uint8_t button_mapping number repeat for each mapped button uint8_t button number uint8_t mapping type int8_t value depending on mapping uint8_t axis mapping number repeated for each mapped axis uint8_t axis number uint8_t mapping type int8_t value depending on mapping uint8_t rotary mapping number repeated for each rotary encoder uint8_t encoder number (note, each encoder has 2 buttons: counter-clockwise and clockwise) uint8_t mapping type int8_t value depending on mapping uint8_t number of macro sequences repeated for every macro sequence const char[] macro sequence, 0-terminated */ CimditProfile::CimditProfile() { m_numProfiles = 0; for (uint8_t i = 0; i < NUMBER_OF_PROFILES; ++i) { m_profiles[i][0] = 0; m_mappedRotary[i].m_number = -1; } m_numMappedButtons = 0; m_numMappedAxis = 0; m_numMappedRotary = 0; m_activeProfile = 0; m_selectingProfile = 0; m_mouseMoveX = 0; m_mouseMoveY = 0; m_eepromPosition = 0; m_stateTimeout = 0; m_numMacros = 0; m_macroStart = nullptr; m_nextBarUpdate = 0; m_dotX = 0; m_dotY = 0; m_relMouseX = 0; m_relMouseY = 0; m_displayState = DISPLAY_BLANK; m_userTimeout = 0; m_stateStarted = 0; m_customImage = 0; memset(m_userString, 0, sizeof(m_userString)); } uint8_t CimditProfile::nextUInt8() { uint8_t ret; #ifdef EXTERNAL_EEPROM Wire.beginTransmission(EXTERNAL_EEPROM); Wire.write((uint8_t)(m_eepromPosition >> 8)); // MSB Wire.write((uint8_t)(m_eepromPosition & 0xFF)); // LSB Wire.endTransmission(); Wire.requestFrom(EXTERNAL_EEPROM, 1); ret = Wire.read(); ++m_eepromPosition; return ret; #else return EEPROM.read(m_eepromPosition++); #endif } uint16_t CimditProfile::nextUInt16() { return ((uint16_t)(nextUInt8()) << 8) + nextUInt8(); } void CimditProfile::nextString(unsigned char* destination) { uint8_t next = nextUInt8(); while (next != 0) { *destination = next; next = nextUInt8(); ++destination; } *destination = 0; } void CimditProfile::begin() { initProfiles(); } void CimditProfile::initProfiles() { m_eepromPosition = 2; // number of profiles m_numProfiles = nextUInt8(); if (m_numProfiles > NUMBER_OF_PROFILES) m_numProfiles = 0; for (uint8_t i = 0; i < m_numProfiles; ++i) { uint16_t position = m_eepromPosition; uint16_t size = nextUInt16(); nextString((unsigned char*)(m_profiles[i])); m_eepromPosition = position + size; // we only scan the names here } if (m_numProfiles>0) { load(0); } } void CimditProfile::load(uint8_t num) { m_activeProfile = num; m_selectingProfile = num; // skip number of profiles m_eepromPosition = 3; // seek to right profile for (uint8_t i = 0; i < num; ++i) { // get profile length uint16_t size = nextUInt16(); m_eepromPosition += size - 2; } // ignore 2 bytes size m_eepromPosition += 2; // m_eepromPosition points to our profile to parse // name is already saved, skip it while (nextUInt8() != 0) { } // do we have a custom image? m_customImage = nextUInt8() > 0 ? m_eepromPosition : 0; if (m_customImage) { // skip 512 bytes m_eepromPosition += 512; } // mapped buttons // number of mapped buttons m_numMappedButtons = nextUInt8(); if (m_numMappedButtons) { for (uint8_t i = 0; i < m_numMappedButtons ; ++i) { m_mappedButtons[i].m_number = nextUInt8(); m_mappedButtons[i].m_type = (MappingType)(nextUInt8()); m_mappedButtons[i].m_value1 = nextUInt8(); if (m_mappedButtons[i].m_type == MIDI_NOTE || m_mappedButtons[i].m_type == MIDI_CTRL || m_mappedButtons[i].m_type == MIDI_PITCH) { m_mappedButtons[i].m_value2 = nextUInt8(); m_mappedButtons[i].m_value3 = nextUInt8(); } } } // end of mapped buttons // mapped axis // number of mapped axis m_numMappedAxis = nextUInt8(); if (m_numMappedAxis) { for (uint8_t i = 0; i < m_numMappedAxis ; ++i) { m_mappedAxis[i].m_number = nextUInt8(); m_mappedAxis[i].m_type = (MappingType)(nextUInt8()); m_mappedAxis[i].m_value1 = nextUInt8(); if (m_mappedAxis[i].m_type == MIDI_CTRL_AXIS) { m_mappedAxis[i].m_value2 = nextUInt8(); } } } // end of mapped axis // mapped rotary // number of mapped rotary m_numMappedRotary = nextUInt8(); if (m_numMappedRotary) { for (uint8_t i = 0; i < m_numMappedRotary ; ++i) { m_mappedRotary[i].m_number = nextUInt8(); m_mappedRotary[i].m_type = (MappingType)(nextUInt8()); m_mappedRotary[i].m_value1 = nextUInt8(); } } // end of mapped rotary m_mouseMoveX = 0; m_mouseMoveY = 0; // parse macro m_numMacros = nextUInt8(); if (m_macroStart) { free(m_macroStart); } m_macroStart = reinterpret_cast(malloc(m_numMacros * sizeof(uint16_t))); for (uint8_t macroNum = 0; macroNum < m_numMacros; ++macroNum) { m_macroStart[macroNum] = m_eepromPosition; scanOrExecuteMacro(macroNum, false); } showActiveProfile(); } void CimditProfile::scanOrExecuteMacro(uint8_t macroNum, bool execute) { m_eepromPosition = m_macroStart[macroNum]; uint8_t value8 = 0; uint16_t delayTime = 0; MACROCOMMANDS token; do { token = (MACROCOMMANDS)nextUInt8(); switch (token) { case MACRO_NULL: break; case MACRO_SPEED: // read in 1 extra numberic value 8 bit delayTime = 1000 / nextUInt8(); break; case MACRO_JOY_PRESS: // read in 1 extra numberic value 8 bit value8 = nextUInt8(); if (execute) { Gamepad.press(value8); Gamepad.write(); } break; case MACRO_JOY_RELEASE: // read in 1 extra numberic value 8 bit value8 = nextUInt8(); if (execute) { Gamepad.release(value8); Gamepad.write(); } break; case MACRO_MOUSE_PRESS: // read in 1 extra numberic value 8 bit value8 = nextUInt8(); if (execute) { Mouse.press(value8); } break; case MACRO_MOUSE_RELEASE: // read in 1 extra numberic value 8 bit value8 = nextUInt8(); if (execute) { Mouse.release(value8); } break; case MACRO_MOUSE_WHEEL: // read in 1 extra numberic value 8 bit value8 = nextUInt8(); if (execute) { Mouse.move(0, 0, value8); } break; case MACRO_DELAY: // read in 1 extra numberic value 16 bit if (execute) { delay(nextUInt16()); } break; case MACRO_KEY_PRESS: // one or more keys while ((value8 = nextUInt8()) != MACRO_END) { if (execute) { Keyboard.press((KeyboardKeycode)value8); if (delayTime) delay(delayTime); } } break; case MACRO_KEY_RELEASE: while ((value8 = nextUInt8()) != MACRO_END) { if (execute) { Keyboard.release((KeyboardKeycode)value8); if (delayTime) delay(delayTime); } } break; case MACRO_TYPE: while ((value8 = nextUInt8()) != MACRO_END) { if (execute) { Keyboard.write((KeyboardKeycode)value8); if (delayTime) delay(delayTime); } } break; case MACRO_MOUSE_REL_X: // read in 1 extra numberic value 8 bit value8 = nextUInt8(); if (execute) { Mouse.move(value8, 0, 0); } break; case MACRO_MOUSE_REL_Y: // read in 1 extra numberic value 8 bit value8 = nextUInt8(); if (execute) { Mouse.move(0, value8, 0); } break; case MACRO_MOUSE_REL_X_AXIS: { // one or 2 bytes" int8_t axis = nextUInt8(); if (axis != -1) { value8 = nextUInt8(); if (execute) { // find previous mapping bool found = false; for (uint8_t i = 0; i < m_numMappedAxis; ++i) { if (m_mappedAxis[i].m_number == axis) { m_mappedAxis[i].m_type = MOUSE_REL_X_AXIS; m_mappedAxis[i].m_value1 = value8; found = true; } } if (!found) { m_mappedAxis[m_numMappedAxis].m_type = MOUSE_REL_X_AXIS; m_mappedAxis[m_numMappedAxis].m_number = axis; m_mappedAxis[m_numMappedAxis++].m_value1 = value8; } } } else { if (execute) { // find axis for (uint8_t i = 0; i < m_numMappedAxis; ++i) { if (m_mappedAxis[i].m_number == axis) { m_mappedAxis[i].m_type = MAPPING_NONE; } } } } } break; case MACRO_MOUSE_REL_Y_AXIS: { // one or 2 bytes int8_t axis = nextUInt8(); if (axis != -1) { value8 = nextUInt8(); if (execute) { // find previous mapping bool found = false; for (uint8_t i = 0; i < m_numMappedAxis; ++i) { if (m_mappedAxis[i].m_number == axis) { m_mappedAxis[i].m_type = MOUSE_REL_Y_AXIS; m_mappedAxis[i].m_value1 = value8; found = true; } } if (!found) { m_mappedAxis[m_numMappedAxis].m_type = MOUSE_REL_Y_AXIS; m_mappedAxis[m_numMappedAxis].m_number = axis; m_mappedAxis[m_numMappedAxis++].m_value1 = value8; } } } else { if (execute) { // find axis for (uint8_t i = 0; i < m_numMappedAxis; ++i) { if (m_mappedAxis[i].m_number == axis) { m_mappedAxis[i].m_type = MAPPING_NONE; } } } } } break; case MACRO_MIDI_NOTEON: { uint8_t channel = nextUInt8(); value8 = nextUInt8(); uint8_t velocity = nextUInt8(); #ifdef HAVE_MIDI if (execute) { midiEventPacket_t noteOn = {0x09, (uint8_t)(0x90 | channel), value8, velocity}; MidiUSB.sendMIDI(noteOn); MidiUSB.flush(); } #endif } break; case MACRO_MIDI_NOTEOFF: { uint8_t channel = nextUInt8(); value8 = nextUInt8(); uint8_t velocity = nextUInt8(); #ifdef HAVE_MIDI if (execute) { midiEventPacket_t noteOff = {0x08, (uint8_t)(0x80 | channel), value8, velocity}; MidiUSB.sendMIDI(noteOff); MidiUSB.flush(); } #endif } break; case MACRO_MIDI_CTRL: { uint8_t channel = nextUInt8(); uint8_t control = nextUInt8(); value8 = nextUInt8(); #ifdef HAVE_MIDI if (execute) { midiEventPacket_t event= {0x0B, (uint8_t)(0xB0 | channel), control, value8}; MidiUSB.sendMIDI(event); MidiUSB.flush(); } #endif } break; case MACRO_MIDI_PITCH: { uint8_t channel = nextUInt8(); uint16_t value = nextUInt16(); #ifdef HAVE_MIDI if (execute) { midiEventPacket_t event = {0x0E, (uint8_t)(0xE0 | channel), (unsigned char)(value&127), (uint8_t)(value>>7)}; MidiUSB.sendMIDI(event); MidiUSB.flush(); } #endif } break; } } while (token != MACRO_NULL); } void CimditProfile::rotaryAction(uint8_t num, int8_t delta) { if (m_displayState != DISPLAY_USER_STRING && m_displayState != DISPLAY_CONFIRMATION) { showActiveProfile(); } // find right action // rotary has counter-clockwise and clockwise mapping num = (num << 1) + (delta > 0); for (uint8_t i = 0; i < m_numMappedRotary; ++i) { if (m_mappedRotary[i].m_number == num) { // rotary supports mouse rel axis, next/prev profile and macro switch (m_mappedRotary[i].m_type) { case MOUSE_REL_X_AXIS: Mouse.move(m_mappedRotary[i].m_value1 * delta, 0, 0); break; case MOUSE_REL_Y_AXIS: Mouse.move(0, m_mappedRotary[i].m_value1 * delta, 0); break; case MOUSE_REL_WHEEL: Mouse.move(0, 0, m_mappedRotary[i].m_value1 * delta); break; case NEXT_PROFILE: case PREV_PROFILE: if (m_mappedRotary[i].m_type == NEXT_PROFILE) { m_selectingProfile = (m_selectingProfile + 1) % m_numProfiles; } else { if (m_selectingProfile > 0) { --m_selectingProfile; } else { m_selectingProfile = m_numProfiles - 1; } } showActivate(); break; case MACRO_PRESS: scanOrExecuteMacro(m_mappedRotary[i].m_value1, true); break; default: break; } } } } void CimditProfile::showActiveProfile() { if (m_displayState != DISPLAY_PROFILE) { #ifdef HAVE_DISPLAY if (m_customImage) { m_eepromPosition = m_customImage; for (uint8_t y = 0; y < SCREEN_HEIGHT; ++y) { for (uint8_t x = 0; x < SCREEN_WIDTH; x += 8) { uint8_t img = nextUInt8(); for (uint8_t t = 0; t < 8; ++t) { display.drawPixel(x + t, y, (img >> (7-t)) & 1); } } } } else { printTextCentered(m_profiles[m_activeProfile]); } #else Serial.println(m_profiles[m_activeProfile]); #endif m_displayState = DISPLAY_PROFILE; display.display(); } m_stateTimeout = hal.m_millis + PROFILE_TIME; m_stateStarted = hal.m_millis; } void CimditProfile::showActivate() { #ifdef HAVE_DISPLAY display.clearDisplay(); display.setTextSize(2,2); display.setCursor(16, 0); display.write("activate"); // default font is 5x8 pixel uint8_t w = (strlen(m_profiles[m_selectingProfile]) + 1) * 2 * 6; uint8_t h = 16; if (w > SCREEN_WIDTH) { display.setTextSize(1,1); w = w >> 1; h = 8; } display.setCursor((SCREEN_WIDTH - w) >> 1, SCREEN_HEIGHT- h); display.write(m_profiles[m_selectingProfile]); display.write("?"); display.display(); #else Serial.print(F("activate ")); Serial.print(m_profiles[m_selectingProfile]); Serial.println(F("?")); #endif m_displayState = DISPLAY_CONFIRMATION; m_stateTimeout = hal.m_millis + SELECTION_TIME; m_stateStarted = hal.m_millis; } void CimditProfile::printTextCentered(const char* text) { display.clearDisplay(); display.setTextSize(2,2); // default font is 6x8 pixel uint8_t w = strlen(text) * 12; uint8_t h = 16; if (w > SCREEN_WIDTH) { display.setTextSize(1,1); w = w >> 1; h = 8; } display.setCursor((SCREEN_WIDTH - w) >> 1, (SCREEN_HEIGHT - h) >> 1); display.write(text); display.display(); } void CimditProfile::showUserString() { #ifdef HAVE_DISPLAY printTextCentered(m_userString); #endif m_displayState = DISPLAY_USER_STRING; m_stateTimeout = hal.m_millis + m_userTimeout; m_stateStarted = hal.m_millis; } void CimditProfile::axisAction(uint8_t num, uint16_t state) { for (uint8_t i = 0; i < m_numMappedAxis; ++i) { if (m_mappedAxis[i].m_number == num) { switch (m_mappedAxis[i].m_type) { case JOYSTICK_AXIS: switch (m_mappedAxis[i].m_value1) { case 1: Gamepad.xAxis(map(state, 0, 1023, -32768, 32767)); break; case 2: Gamepad.yAxis(map(state, 0, 1023, -32768, 32767)); break; case 3: Gamepad.zAxis(map(state, 0, 1023, -128, 127)); break; case 4: Gamepad.rxAxis(map(state, 0, 1023, -32768, 32767)); break; case 5: Gamepad.ryAxis(map(state, 0, 1023, -32768, 32767)); break; case 6: Gamepad.rzAxis(map(state, 0, 1023, -128, 127)); break; } break; case MOUSE_REL_X_AXIS: { int16_t pos = (int16_t)state - 512; if (pos > JOYSTICK_DEAD_ZONE_X) { m_mouseMoveX = map(pos, JOYSTICK_DEAD_ZONE_X, 511, 0, m_mappedAxis[i].m_value1); } else if (pos < -JOYSTICK_DEAD_ZONE_X) { m_mouseMoveX = map(pos, -512, -JOYSTICK_DEAD_ZONE_X, -m_mappedAxis[i].m_value1, 0); } else { m_mouseMoveX = 0; } } break; case MOUSE_REL_Y_AXIS: { int16_t pos = (int16_t)state - 512; if (pos > JOYSTICK_DEAD_ZONE_Y) { m_mouseMoveY = map(pos, JOYSTICK_DEAD_ZONE_Y, 511, 0, m_mappedAxis[i].m_value1); } else if (pos < -JOYSTICK_DEAD_ZONE_Y) { m_mouseMoveY = map(pos, -512, -JOYSTICK_DEAD_ZONE_Y, -m_mappedAxis[i].m_value1, 0); } else { m_mouseMoveY = 0; } } break; case MIDI_CTRL_AXIS: #ifdef HAVE_MIDI { midiEventPacket_t event = {0x0B, (uint8_t)(0xB0 | m_mappedAxis[i].m_value1), m_mappedAxis[i].m_value2, (uint8_t)(state/8)}; MidiUSB.sendMIDI(event); MidiUSB.flush(); } #endif break; case MIDI_PITCH_AXIS: #ifdef HAVE_MIDI { uint16_t val = (1023-state) * 16; midiEventPacket_t event = {0x0E, (uint8_t)(0xE0 | m_mappedAxis[i].m_value1), (unsigned char)(val&127), (uint8_t)(val>>7)}; MidiUSB.sendMIDI(event); MidiUSB.flush(); } #endif break; case MAPPING_NONE: case JOYSTICK_BUTTON: case MOUSE_BUTTON: case MOUSE_REL_WHEEL: case NEXT_PROFILE: case PREV_PROFILE: case SWITCH_PROFILE: case MACRO_PRESS: case MACRO_RELEASE: case KEYBOARD_BUTTON: case MIDI_NOTE: case MIDI_CTRL: case MIDI_PITCH: break; } } } } void CimditProfile::buttonAction(uint8_t num, bool state) { if (m_displayState != DISPLAY_USER_STRING && m_displayState != DISPLAY_CONFIRMATION) { showActiveProfile(); } for (uint8_t i = 0; i < m_numMappedButtons; ++i) { if (m_mappedButtons[i].m_number == num) { switch (m_mappedButtons[i].m_type) { case JOYSTICK_BUTTON: if (state) { Gamepad.press(m_mappedButtons[i].m_value1); } else { Gamepad.release(m_mappedButtons[i].m_value1); } Gamepad.write(); break; case MOUSE_BUTTON: if (state) { Mouse.press(m_mappedButtons[i].m_value1); } else { Mouse.release(m_mappedButtons[i].m_value1); } break; case MOUSE_REL_X_AXIS: Mouse.move(m_mappedButtons[i].m_value1, 0, 0); break; case MOUSE_REL_Y_AXIS: Mouse.move(0, m_mappedButtons[i].m_value1, 0); break; case MOUSE_REL_WHEEL: Mouse.move(0, 0, m_mappedButtons[i].m_value1); break; case NEXT_PROFILE: case PREV_PROFILE: if (m_mappedRotary[i].m_type == NEXT_PROFILE) { m_selectingProfile = (m_selectingProfile + 1) % m_numProfiles; } else { if (m_selectingProfile > 0) { --m_selectingProfile; } else { m_selectingProfile = m_numProfiles - 1; } } showActivate(); break; case SWITCH_PROFILE: if (m_stateTimeout && state && m_displayState == DISPLAY_CONFIRMATION) { load(m_selectingProfile); } break; case MACRO_PRESS: if (state) scanOrExecuteMacro(m_mappedButtons[i].m_value1, true); break; case MACRO_RELEASE: if (!state) scanOrExecuteMacro(m_mappedButtons[i].m_value1, true); break; case KEYBOARD_BUTTON: if (state) { Keyboard.press((KeyboardKeycode)m_mappedButtons[i].m_value1); } else { Keyboard.release((KeyboardKeycode)m_mappedButtons[i].m_value1); } break; case MIDI_NOTE: #ifdef HAVE_MIDI if (state) { // send a midi note on message midiEventPacket_t noteOn = {0x09, (uint8_t)(0x90 | m_mappedButtons[i].m_value1), m_mappedButtons[i].m_value2, m_mappedButtons[i].m_value3}; MidiUSB.sendMIDI(noteOn); } else { // send a midi note on message midiEventPacket_t noteOff = {0x08, (uint8_t)(0x80 | m_mappedButtons[i].m_value1), m_mappedButtons[i].m_value2, m_mappedButtons[i].m_value3}; MidiUSB.sendMIDI(noteOff); } MidiUSB.flush(); #endif break; case MIDI_CTRL: #ifdef HAVE_MIDI { // send a midi note on message midiEventPacket_t control = {0x0B, (uint8_t)(0xB0 | m_mappedButtons[i].m_value1), m_mappedButtons[i].m_value2, m_mappedButtons[i].m_value3}; MidiUSB.sendMIDI(control); MidiUSB.flush(); } #endif break; case MIDI_PITCH: #ifdef HAVE_MIDI { // send a midi note on message midiEventPacket_t event = {0x0E, (uint8_t)(0xE0 | m_mappedButtons[i].m_value1), m_mappedButtons[i].m_value2, m_mappedButtons[i].m_value3}; MidiUSB.sendMIDI(event); MidiUSB.flush(); } #endif break; case MAPPING_NONE: case JOYSTICK_AXIS: case MIDI_CTRL_AXIS: case MIDI_PITCH_AXIS: break; } } } } void CimditProfile::tick() { if (m_stateTimeout && hal.m_millis >= m_stateTimeout) { switch (m_displayState) { case DISPLAY_BLANK: // ignored break; case DISPLAY_PROFILE: // go to blank display state display.clearDisplay(); display.display(); m_displayState = DISPLAY_BLANK; m_stateTimeout = 0; m_stateStarted = hal.m_millis; break; case DISPLAY_CONFIRMATION: case DISPLAY_USER_STRING: // go back to profile name showActiveProfile(); m_selectingProfile = m_activeProfile; break; } } if (hal.m_millis > m_nextBarUpdate) { m_nextBarUpdate = hal.m_millis + BAR_UPDATE_TIME; if (m_stateTimeout) { uint8_t percentage = SCREEN_WIDTH * (hal.m_millis - m_stateStarted) / (m_stateTimeout - m_stateStarted); // white bar from left to percentage display.drawFastHLine(0, SCREEN_HEIGHT-1, percentage, true); // black bar from percentage to right display.drawFastHLine(percentage + 1, SCREEN_HEIGHT-1, SCREEN_WIDTH - percentage - 1, false); display.displayPartial(3); } if (m_displayState == DISPLAY_BLANK) { // clear old pixel display.drawPixel(m_dotX, m_dotY, false); uint8_t part1 = m_dotY/8; uint16_t r = rand(); m_dotX = r & 127; m_dotY = r & 31; display.drawPixel(m_dotX, m_dotY, true); uint8_t part2 = m_dotY/8; if (part1 != part2) { display.displayPartial(part2); } display.displayPartial(part1); } } if (m_mouseMoveX || m_mouseMoveY) { m_relMouseX += (float)m_mouseMoveX / 50; m_relMouseY += (float)m_mouseMoveY / 50; if (fabs(m_relMouseX) >= 1 || fabs(m_relMouseY) >= 1) { Mouse.move(m_relMouseX, m_relMouseY); m_relMouseX -= (int8_t)m_relMouseX; m_relMouseY -= (int8_t)m_relMouseY; } } } void CimditProfile::printFlash() { const uint8_t eepromPosition = m_eepromPosition; m_eepromPosition = 0; uint16_t len = nextUInt16(); m_eepromPosition = 0; #ifdef EXTERNAL_EEPROM if (len < 32768) { #else if (len < 1024) { #endif uint8_t in; for (uint16_t i = 0; i < len; ++i) { in = nextUInt8(); Serial.print(in >> 4, 16); Serial.print(in & 15, 16); } Serial.println(); } m_eepromPosition = eepromPosition; } #ifdef EXTERNAL_EEPROM void CimditProfile::eepromWrite(uint16_t addr, uint8_t val) { Wire.beginTransmission(EXTERNAL_EEPROM); Wire.write((uint8_t)(addr >> 8)); // MSB Wire.write((uint8_t)(addr & 0xFF)); // LSB Wire.write(val); Wire.endTransmission(); delay(5); } #endif void CimditProfile::writeFlash() { char buf[5]= {0}; Serial.readBytes(buf, 4); uint16_t len = strtol(buf, NULL, 16); buf[2] = 0; m_eepromPosition = 0; #ifdef EXTERNAL_EEPROM eepromWrite(m_eepromPosition++, len >> 8); eepromWrite(m_eepromPosition++, len & 255); if (len < 32768) { #else EEPROM.write(m_eepromPosition++, len >> 8); EEPROM.write(m_eepromPosition++, len & 255); if (len < 1024) { #endif for (uint16_t i = 2; i < len; ++i) { while ((buf[0] = Serial.read()) == -1) { delay(100); } while ((buf[1] = Serial.read()) == -1) { delay(100); } uint8_t in = strtol(buf, NULL, 16); #ifdef EXTERNAL_EEPROM eepromWrite(m_eepromPosition++, in); #else EEPROM.write(m_eepromPosition++, in); #endif } Serial.println("written"); initProfiles(); } m_displayState = DISPLAY_BLANK; // to enforce a redraw showActiveProfile(); } void CimditProfile::setUserString(uint8_t timeout, const char* input) { strncpy(m_userString, input, sizeof(m_userString) - 1); m_userTimeout = timeout * 1000; showUserString(); } void CimditProfile::userDisplay() { uint8_t timeout = 0; char buf = 0; // read number for time to display do { buf = Serial.read(); if (buf != '\n') { timeout = timeout * 10 + buf - '0'; } } while (buf != '\n'); Serial.println(F("ready for .pbm file")); // read header do { buf = Serial.read(); } while (buf != '\n'); while (!Serial.available()) { delay(10); }; buf = Serial.read(); if (buf == '#') { // a comment do { buf = Serial.read(); } while (buf != '\n'); } while (!Serial.available()) { delay(10); }; // width height uint8_t width = 0; do { buf = Serial.read(); if (buf != ' ') { width = width * 10 + buf - '0'; } } while (buf != ' '); uint8_t height = 0; do { buf = Serial.read(); if (buf != '\n') { height = height * 10 + buf - '0'; } } while (buf != '\n'); buf = 0; uint16_t len = width * height; if (width != SCREEN_WIDTH || SCREEN_HEIGHT != 32) { for (uint8_t i = 0; i < len; ++i) { buf = Serial.read(); if (buf == '\n') buf = Serial.read(); } return; } for (uint8_t y = 0; y < SCREEN_HEIGHT; ++y) { for (uint8_t x = 0; x < SCREEN_WIDTH; ++x) { while ((buf = Serial.read()) == -1) { delay(100); } while (buf != '0' && buf != '1') { buf = Serial.read(); } display.drawPixel(x, y, buf == '1'); } } display.display(); m_displayState = DISPLAY_USER_STRING; uint32_t now = millis(); m_stateTimeout = now + (uint32_t)timeout * 1000; m_stateStarted = now; }