///////////////////////////////////////////////////////////////////////////////////////////////////////// // // Name: Mouse.cpp // Created: Mar 2022 // Version: v1.0 // Author(s): Philip Smart // Description: PS/2 Mouse to Sharp Host Interface logic. // This source file contains the singleton class containing logic to obtain // PS/2 mouse data (position, keys etc), map them into Sharp compatible codes and // transmit the data to the connected host. // // The whole application of which this class is a member, uses the Espressif Development // environment with Arduino components. // // Credits: // Copyright: (c) 2022 Philip Smart // // History: Mar 2022 - Initial write. // v1.01 May 2022 - Initial release version. // v1.02 Jun 2022 - Updates to reflect changes realised in other modules due to addition of // bluetooth and suspend logic due to NVS issues using both cores. // Updates to reflect moving functionality into the HID and to support // Bluetooth as a primary mouse or secondary mouse. // // Notes: See Makefile to enable/disable conditional components // ///////////////////////////////////////////////////////////////////////////////////////////////////////// // This source file is free software: you can redistribute it and#or modify // it under the terms of the GNU General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This source file is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . ///////////////////////////////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "driver/uart.h" #include "driver/gpio.h" #include "esp_log.h" #include "Arduino.h" #include "soc/timer_group_struct.h" #include "soc/timer_group_reg.h" #include "driver/timer.h" #include "sdkconfig.h" #include "Mouse.h" // Tag for ESP main application logging. #define MAINTAG "Mouse" // Mouse Protocol // -------------- // // // The Sharp (X68000/X1/MZ-2500/MZ-2800) mouse uses an asynchronous serial protocol over two wires (MSDATA/MSCTRL). // The MSCTRL signal is an enable signal, idle state = HIGH, it goes low prior to transmission of data by at least 1mS and goes high after // transmission of last bit by ~2.56mS. // The MSDATA signal is a standard asynchronous signal, idle = HIGH, 1 start bit, 8 data bits, 2 stop bits @ 4800baud. // // Protocol: // Idle State (MSDATA/MSCTRL) = High. // Transmission: MSCTRL -> LOW // 1ms delay // MSDATA -> low, first start bit. // 3 bytes transmitted in a <1xStart><8xdata><2xstop> format. // MSDATA -> high // 2.56ms delay. // MSCTRL -> HIGH // Data bytes: // CTRL = [7] - Mouse rolling forward when high, backward when low. // [6] // [5] - Mouse rolling left, right when low. // [4] // [3] // [2] // [1] - Right button pressed = HIGH. // [0] - Left button pressed = HIGH. // POS X [7:0] - X Position data. // POS Y [7:0] - Y Position data. // Method to realise the Sharp host Mouse protocol. // This method uses Core 1 and it will hold it in a spinlock as necessary to ensure accurate timing. // Mouse data is passed into the method via a direct object, using the FreeRTOS Queue creates a time lag resulting in the mouse data being out of sync with hand movement. IRAM_ATTR void Mouse::hostInterface( void * pvParameters ) { // Locals. // Mouse* pThis = (Mouse*)pvParameters; // Retrieve pointer to object in order to access data. bool msctrlEdge = false; uint8_t txBuf[4]; uint32_t MSCTRL_MASK; uint32_t MSDATA_MASK; #ifdef CONFIG_HOST_BITBANG_UART int txPos; int txCnt; uint32_t shiftReg; uint64_t delayTimer = 0LL; uint64_t curTime = 0LL; uint32_t bitCount = 0; enum HOSTXMITSTATE { FSM_IDLE = 0, FSM_STARTXMIT = 1, FSM_STARTBIT = 2, FSM_DATA = 3, FSM_STOP = 4, FSM_ENDXMIT = 5 } state = FSM_IDLE; #endif // Initialise the MUTEX which prevents this core from being released to other tasks. pThis->x1Mutex = portMUX_INITIALIZER_UNLOCKED; if(pThis->hostControl.secondaryIf == false) { MSCTRL_MASK = (1 << CONFIG_HOST_KDB0); MSDATA_MASK = (1 << CONFIG_HOST_KDB1); } else { MSCTRL_MASK = (1 << CONFIG_HOST_KDB0); MSDATA_MASK = (1 << CONFIG_HOST_KDI4); } gpio_config_t ioConf; ioConf.intr_type = GPIO_INTR_DISABLE; ioConf.mode = GPIO_MODE_INPUT; ioConf.pull_down_en = GPIO_PULLDOWN_DISABLE; ioConf.pull_up_en = GPIO_PULLUP_ENABLE; // Both Hardware UART and bitbang need MSCTRL setting as an input. if(pThis->hostControl.secondaryIf == false) { ioConf.pin_bit_mask = (1ULL<hostControl.secondaryIf == false) { ioConf.pin_bit_mask = (1ULL<hostControl.secondaryIf == false) || curTime >= delayTimer) { // Ensure the timer is stopped. timer_pause(TIMER_GROUP_0, TIMER_0); delayTimer = 0LL; // Finite state machine to retrieve a key for transmission then serialise it according to the X1 protocol. switch(state) { case FSM_IDLE: // Yield if the suspend flag is set. pThis->yield(0); // Check stack space, report if it is getting low. if(uxTaskGetStackHighWaterMark(NULL) < 1024) { ESP_LOGW(MAINTAG, "THREAD STACK SPACE(%d)\n",uxTaskGetStackHighWaterMark(NULL)); } if(pThis->hostControl.secondaryIf == false) { // Detect high to low edge. On mouse primary mode the MSCTRL signal forces the tempo. On mouse secondary mode (operating in tandem to keyboard), // the timer forces the tempo. // msctrlEdge = (REG_READ(GPIO_IN_REG) & MSCTRL_MASK) != 0 ? true : msctrlEdge; } // Wait for a window when MSCTRL goes low. if(pThis->hostControl.secondaryIf == true || (msctrlEdge == true && (REG_READ(GPIO_IN_REG) & MSCTRL_MASK) == 0)) { // Wait for incoming mouse movement message. if(pThis->xmitMsg.valid) { txBuf[0] = (uint8_t)pThis->xmitMsg.status; txBuf[1] = (uint8_t)pThis->xmitMsg.xPos; txBuf[2] = (uint8_t)pThis->xmitMsg.yPos; pThis->xmitMsg.valid = false; // Shouldnt be a race state here but consider a mutex if mouse gets out of sync. txBuf[3] = 0x00; txPos = 0; txCnt = 3; } else { // Sharp host protocol requires us to send zero change messages on a regular period regardless of new data. txBuf[0] = 0x00; txBuf[1] = 0x00; txBuf[2] = 0x00; txBuf[3] = 0x00; txPos = 0; txCnt = 3; } // Advance to first start bit. state = FSM_STARTXMIT; // Clear edge detect for next loop. msctrlEdge = false; } break; case FSM_STARTXMIT: // Ensure all variables and states correct before entering serialisation. GPIO.out_w1ts = MSDATA_MASK; state = FSM_STARTBIT; bitCount = 8; shiftReg = txBuf[txPos++]; txCnt--; // Create, initialise and hold a spinlock so the current core is bound to this one method. portENTER_CRITICAL(&pThis->x1Mutex); break; case FSM_STARTBIT: // Send out the start bit by bringing MSDATA low for 208us (4800 baud 1bit time period). GPIO.out_w1tc = MSDATA_MASK; delayTimer = BITBANG_UART_BIT_TIME; state = FSM_DATA; break; case FSM_DATA: if(bitCount > 0) { // Setup the bit on MSDATA if(shiftReg & 0x00000001) { GPIO.out_w1ts = MSDATA_MASK; } else { GPIO.out_w1tc = MSDATA_MASK; } // Shift the data to the next bit for transmission. shiftReg = shiftReg >> 1; // 1 bit period. delayTimer = BITBANG_UART_BIT_TIME; // 1 Less bit in frame. bitCount--; } else { state = FSM_STOP; } break; case FSM_STOP: // Send out the stop bit, 2 are needed so just adjust the time delay. GPIO.out_w1ts = MSDATA_MASK; delayTimer = BITBANG_UART_BIT_TIME * 2; state = FSM_ENDXMIT; break; case FSM_ENDXMIT: // End of critical timing loop, release the core so other tasks can run whilst we load up the next byte. portEXIT_CRITICAL(&pThis->x1Mutex); // Any more bytes to transmit, loop and send if there are. if(txCnt > 0) { state = FSM_STARTXMIT; } else { // Reset timer for next loop. delayTimer = 20000UL; state = FSM_IDLE; } break; } // If a new delay is requested, set the value into the timer and start. if(delayTimer > 0LL) { timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0LL); timer_start(TIMER_GROUP_0, TIMER_0); } } #endif #ifdef CONFIG_HOST_HW_UART // Get the current timer value, we need to wait 20ms between transmissions. timer_get_counter_value(TIMER_GROUP_0, TIMER_0, &curTime); if(curTime >= delayTimer) { // Wait for a window when MSCTRL goes low. if(pThis->hostControl.secondaryIf == true || (REG_READ(GPIO_IN_REG) & MSCTRL_MASK) == 0) { // Ensure the timer is stopped, initialise to 0 and restart. timer_pause(TIMER_GROUP_0, TIMER_0); delayTimer = 20000LL; timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0LL); timer_start(TIMER_GROUP_0, TIMER_0); // Wait for incoming mouse movement message. if(pThis->xmitMsg.valid) { txBuf[0] = (uint8_t)pThis->xmitMsg.status; txBuf[1] = (uint8_t)pThis->xmitMsg.xPos; txBuf[2] = (uint8_t)pThis->xmitMsg.yPos; pThis->xmitMsg.valid = false; // Shouldnt be a race state here but consider a mutex if mouse gets out of sync. txBuf[3] = 0x00; txPos = 0; txCnt = 3; } else { // Sharp host protocol requires us to send zero change messages on a regular period regardless of new data. txBuf[0] = 0x00; txBuf[1] = 0x00; txBuf[2] = 0x00; txBuf[3] = 0x00; txPos = 0; txCnt = 3; } // Send the bytes and wait. uart_write_bytes(pThis->hostControl.uartNum, (const char *)txBuf, 3); // This method doesnt actually return after the last byte is transmitted, it returns well before, so we tack on a 10ms delay which is the width for 3 bytes at 4800 baud. uart_wait_tx_done(pThis->hostControl.uartNum, 25000); vTaskDelay(10); } // Check stack space, report if it is getting low. if(uxTaskGetStackHighWaterMark(NULL) < 1024) { ESP_LOGW(MAPKEYTAG, "THREAD STACK SPACE(%d)\n",uxTaskGetStackHighWaterMark(NULL)); } // Yield if the suspend flag is set. pThis->yield(0); } #endif // Logic to feed the watchdog if needed. Watchdog disabled in menuconfig but if enabled this will need to be used. //TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable //TIMERG0.wdt_feed=1; // feed dog //TIMERG0.wdt_wprotect=0; // write protect //TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable //TIMERG1.wdt_feed=1; // feed dog //TIMERG1.wdt_wprotect=0; // write protect } } // Primary HID routine. // This method is responsible for receiving HID (PS/2 or BT) mouse scan data and mapping it into Sharp compatible mouse data. // The HID mouse data once received is mapped and pushed onto a FIFO queue for transmission to the host. // void Mouse::mouseReceiveData(HID::t_mouseMessageElement mouseMessage) { // Locals. uint8_t status; // Invert Y as the Sharp host is inverted compared to a PS/2 on the Y axis. mouseMessage.yPos = -mouseMessage.yPos; // Initialise the status flag, on the Sharp host it is <1><0> status = (((mouseMessage.xPos >> 8) & 0x01) << 4) | (mouseMessage.status & 0x0F ); // Check bounds and set flags accordingly. if(mouseMessage.xPos > 127) { mouseMessage.xPos = 127; // Maximum resolution of Sharp host X movement. status |= (1UL << 4); // Set overflow bit. } if(mouseMessage.xPos < -128) { mouseMessage.xPos = -128; // Minimum resolution of Sharp host X movement. status |= (1UL << 5); // Set underflow bit. } if(mouseMessage.yPos > 127) { mouseMessage.yPos = 127; // Maximum resolution of Sharp host Y movement. status |= (1UL << 6); // Set overflow bit. } if(mouseMessage.yPos < -128) { mouseMessage.yPos = -128; // Minimum resolution of Sharp host Y movement. status |= (1UL << 7); // Set underflow bit. } // Convert back to 8bit 2's compliment and store in the host message to the host thread. xmitMsg.xPos = (int8_t)mouseMessage.xPos; xmitMsg.yPos = (int8_t)mouseMessage.yPos; xmitMsg.status = status; xmitMsg.wheel = mouseMessage.wheel; xmitMsg.valid = true; return; } // A method to return the Type of data for a given column in the KeyMap table. // void Mouse::getMouseConfigTypes(std::vector& typeList) { // Add the types. // typeList.push_back(HID_MOUSE_HOST_SCALING_TYPE); typeList.push_back(HID_MOUSE_SCALING_TYPE); typeList.push_back(HID_MOUSE_RESOLUTION_TYPE); typeList.push_back(HID_MOUSE_SAMPLING_TYPE); return; } // Method to return a list of key:value entries for a given config category. This represents the // feature which can be selected and the value it uses. Features can be combined by ORing the values // together. bool Mouse::getMouseSelectList(std::vector>& selectList, std::string option) { // Locals. // bool result = true; // Build up a map, depending on the list required, of name to value. This list can then be used // by a user front end to select an option based on a name and return its value. if(option.compare(HID_MOUSE_HOST_SCALING_TYPE) == 0) { selectList.push_back(std::make_pair("ACTIVE", mouseConfig.host.scaling)); selectList.push_back(std::make_pair(HID_MOUSE_HOST_SCALING_1_1_NAME, HID::HID_MOUSE_HOST_SCALING_1_1)); selectList.push_back(std::make_pair(HID_MOUSE_HOST_SCALING_1_2_NAME, HID::HID_MOUSE_HOST_SCALING_1_2)); selectList.push_back(std::make_pair(HID_MOUSE_HOST_SCALING_1_3_NAME, HID::HID_MOUSE_HOST_SCALING_1_3)); selectList.push_back(std::make_pair(HID_MOUSE_HOST_SCALING_1_4_NAME, HID::HID_MOUSE_HOST_SCALING_1_4)); selectList.push_back(std::make_pair(HID_MOUSE_HOST_SCALING_1_5_NAME, HID::HID_MOUSE_HOST_SCALING_1_5)); } else if(option.compare(HID_MOUSE_SCALING_TYPE) == 0) { selectList.push_back(std::make_pair("ACTIVE", mouseConfig.mouse.scaling)); selectList.push_back(std::make_pair(HID_MOUSE_SCALING_1_1_NAME, HID::HID_MOUSE_SCALING_1_1)); selectList.push_back(std::make_pair(HID_MOUSE_SCALING_2_1_NAME, HID::HID_MOUSE_SCALING_2_1)); } else if(option.compare(HID_MOUSE_RESOLUTION_TYPE) == 0) { selectList.push_back(std::make_pair("ACTIVE", mouseConfig.mouse.resolution)); selectList.push_back(std::make_pair(HID_MOUSE_RESOLUTION_1_1_NAME, HID::HID_MOUSE_RESOLUTION_1_1)); selectList.push_back(std::make_pair(HID_MOUSE_RESOLUTION_1_2_NAME, HID::HID_MOUSE_RESOLUTION_1_2)); selectList.push_back(std::make_pair(HID_MOUSE_RESOLUTION_1_4_NAME, HID::HID_MOUSE_RESOLUTION_1_4)); selectList.push_back(std::make_pair(HID_MOUSE_RESOLUTION_1_8_NAME, HID::HID_MOUSE_RESOLUTION_1_8)); } else if(option.compare(HID_MOUSE_SAMPLING_TYPE) == 0) { selectList.push_back(std::make_pair("ACTIVE", mouseConfig.mouse.sampleRate)); selectList.push_back(std::make_pair(HID_MOUSE_SAMPLE_RATE_10_NAME, HID::HID_MOUSE_SAMPLE_RATE_10)); selectList.push_back(std::make_pair(HID_MOUSE_SAMPLE_RATE_20_NAME, HID::HID_MOUSE_SAMPLE_RATE_20)); selectList.push_back(std::make_pair(HID_MOUSE_SAMPLE_RATE_40_NAME, HID::HID_MOUSE_SAMPLE_RATE_40)); selectList.push_back(std::make_pair(HID_MOUSE_SAMPLE_RATE_60_NAME, HID::HID_MOUSE_SAMPLE_RATE_60)); selectList.push_back(std::make_pair(HID_MOUSE_SAMPLE_RATE_80_NAME, HID::HID_MOUSE_SAMPLE_RATE_80)); selectList.push_back(std::make_pair(HID_MOUSE_SAMPLE_RATE_100_NAME, HID::HID_MOUSE_SAMPLE_RATE_100)); selectList.push_back(std::make_pair(HID_MOUSE_SAMPLE_RATE_200_NAME, HID::HID_MOUSE_SAMPLE_RATE_200)); } else { // Not found! result = false; } // Return result, false if the option not found, true otherwise. // return(result); } // Public method to set the mouse configuration parameters. // bool Mouse::setMouseConfigValue(std::string paramName, std::string paramValue) { // Locals. // bool dataError = false; int value(0); std::stringstream testVal(paramValue); // Match the parameter name to a known mouse parameter, type and data check the parameter value and assign to the config accordingly. if(paramName.compare(HID_MOUSE_HOST_SCALING_TYPE) == 0) { // Exception handling is disabled, stringstream is used to catch bad input. dataError = (static_cast(testVal >> value) ? false : true); if(dataError == false) { if(value >= to_underlying(HID::HID_MOUSE_HOST_SCALING_1_1) && value <= to_underlying(HID::HID_MOUSE_HOST_SCALING_1_5)) { mouseConfig.host.scaling = static_cast(value); hid->setMouseHostScaling(mouseConfig.host.scaling); } else { dataError = true; } } } if(paramName.compare(HID_MOUSE_SCALING_TYPE) == 0) { dataError = (static_cast(testVal >> value) ? false : true); if(dataError == false) { if(value >= to_underlying(HID::HID_MOUSE_SCALING_1_1) && value <= to_underlying(HID::HID_MOUSE_SCALING_2_1)) { mouseConfig.mouse.scaling = static_cast(value); hid->setMouseScaling(mouseConfig.mouse.scaling); } else { dataError = true; } } } if(paramName.compare(HID_MOUSE_RESOLUTION_TYPE) == 0) { dataError = (static_cast(testVal >> value) ? false : true); if(dataError == false) { if(value >= to_underlying(HID::HID_MOUSE_RESOLUTION_1_1) && value <= to_underlying(HID::HID_MOUSE_RESOLUTION_1_8)) { mouseConfig.mouse.resolution = static_cast(value); hid->setMouseResolution(mouseConfig.mouse.resolution); } else { dataError = true; } } } if(paramName.compare(HID_MOUSE_SAMPLING_TYPE) == 0) { dataError = (static_cast(testVal >> value) ? false : true); if(dataError == false) { if(value >= to_underlying(HID::HID_MOUSE_SAMPLE_RATE_10) && value <= to_underlying(HID::HID_MOUSE_SAMPLE_RATE_200)) { mouseConfig.mouse.sampleRate = static_cast(value); hid->setMouseSampleRate(mouseConfig.mouse.sampleRate); } else { dataError = true; } } } // Error = true, success = false. return(dataError); } // Method to save (persist) the configuration into NVS RAM. bool Mouse::persistConfig(void) { // Locals. bool result = true; // Persist the data for next time. if(nvs->persistData(getClassName(__PRETTY_FUNCTION__), &this->mouseConfig, sizeof(t_mouseConfig)) == false) { ESP_LOGW(MAINTAG, "Persisting Mouse configuration data failed, check NVS setup.\n"); result = false; } // Few other updates so make a commit here to ensure data is flushed and written. else if(nvs->commitData() == false) { ESP_LOGW(MAINTAG, "NVS Commit writes operation failed, some previous writes may not persist in future power cycles."); } // Request persistence in the HID module. result |= hid->persistConfig(); // Error = false, success = true. return(result); } // Initialisation routine. Start two threads, one to handle the incoming PS/2 mouse data and map it, the second to handle the host interface. void Mouse::init(uint32_t ifMode, NVS *hdlNVS, LED *hdlLED, HID *hdlHID) { // Initialise control variables. #ifdef CONFIG_HOST_HW_UART hostControl.uartNum = UART_NUM_2; hostControl.uartBufferSize = 256; hostControl.uartQueueSize = 10; #endif // Initialise the basic components. init(hdlNVS, hdlHID); // Invoke the prototype init which initialises common variables and devices shared by all subclass. KeyInterface::init(getClassName(__PRETTY_FUNCTION__), hdlNVS, hdlLED, hdlHID, ifMode); // There are two build possibilities, hardware UART and BITBANG. I initially coded using hardware but whilst trying to find a bug, wrote a bitbang // technique and both are fit for purpose, so enabling either yields the same result. #ifdef CONFIG_HOST_HW_UART // Prepare the UART to be used for communications with the Sharp host. // The Sharp host Mouse uses an Asynchronous protocol with 2 stop bits no parity 4800 baud. // uart_config_t uartConfig = { .baud_rate = 4800, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_2, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, .rx_flow_ctrl_thresh = 122, .source_clk = UART_SCLK_APB, }; // Configure UART parameters and pin assignments, software flow control, not RTS/CTS. // The mouse only uses a Tx line, the MSCTRL line is used as a gate signal, so assign the Rx line to an unused pin. ESP_ERROR_CHECK(uart_param_config(hostControl.uartNum, &uartConfig)); ESP_ERROR_CHECK(uart_set_pin(hostControl.uartNum, CONFIG_HOST_KDB1, CONFIG_HOST_KDB2, -1, -1)); // Install UART driver. Use RX/TX buffers without event queue. ESP_ERROR_CHECK(uart_driver_install(hostControl.uartNum, hostControl.uartBufferSize, hostControl.uartBufferSize, 0, NULL, 0)); #endif // Register the streaming callback for the mouse, this will receive data, process it and send to the hostInterface for transmission to the host. hid->setDataCallback(&Mouse::mouseReceiveData, this); // Create a task pinned to core 1 which will fulfill the Sharp Mouse host interface. This task has the highest priority // and it will also hold spinlock and manipulate the watchdog to ensure a scan cycle timing can be met. This means // all other tasks running on Core 1 will suspend as needed. The HID mouse controller will be serviced with core 0. // // Core 1 - Sharp Mouse Host Interface ESP_LOGW(MAINTAG, "Starting mouseIf thread..."); ::xTaskCreatePinnedToCore(&this->hostInterface, "mouseIf", 4096, this, 25, &this->TaskHostIF, 1); vTaskDelay(500); } // Initialisation routine without hardware. void Mouse::init(NVS *hdlNVS, HID *hdlHID) { // Invoke the prototype init which initialises common variables and devices shared by all subclass. KeyInterface::init(getClassName(__PRETTY_FUNCTION__), hdlNVS, hdlHID); // Retrieve configuration, if it doesnt exist, set defaults. // if(nvs->retrieveData(getClassName(__PRETTY_FUNCTION__), &this->mouseConfig, sizeof(t_mouseConfig)) == false) { ESP_LOGW(MAINTAG, "Mouse configuration set to default, no valid config in NVS found."); mouseConfig.mouse.resolution= HID::HID_MOUSE_RESOLUTION_1_8; mouseConfig.mouse.scaling = HID::HID_MOUSE_SCALING_1_1; mouseConfig.mouse.sampleRate= HID::HID_MOUSE_SAMPLE_RATE_60; mouseConfig.host.scaling = HID::HID_MOUSE_HOST_SCALING_1_2; // Persist the data for next time. if(nvs->persistData(getClassName(__PRETTY_FUNCTION__), &this->mouseConfig, sizeof(t_mouseConfig)) == false) { ESP_LOGW(MAINTAG, "Persisting Default Mouse configuration data failed, check NVS setup.\n"); } // Few other updates so make a commit here to ensure data is flushed and written. else if(nvs->commitData() == false) { ESP_LOGW(MAINTAG, "NVS Commit writes operation failed, some previous writes may not persist in future power cycles."); } } } // Constructor, basically initialise the Singleton interface and let the threads loose. Mouse::Mouse(uint32_t ifMode, NVS *hdlNVS, LED *hdlLED, HID *hdlHID) { // Operating in uni-mode. hostControl.secondaryIf = false; // Initialise the interface init(ifMode, hdlNVS, hdlLED, hdlHID); } // Constructor, basic initialisation without hardware. Mouse::Mouse(NVS *hdlNVS, HID *hdlHID) { // Operating in uni-mode. hostControl.secondaryIf = false; // Initialise the interface init(hdlNVS, hdlHID); } // Constructor for use when mouse operates in tandem with a keyboard. Mouse::Mouse(uint32_t ifMode, NVS *hdlNVS, LED *hdlLED, HID *hdlHID, bool secondaryIf) { // The interface can act in primary mode, ie. sole interface or secondary mode where it acts in tandem to a keyboard host. Slight processing differences occur // in secondary mode, for example, the pin used to output mouse data differs. hostControl.secondaryIf = secondaryIf; // Initialise the interface init(ifMode, hdlNVS, hdlLED, hdlHID); } // Constructor, used for version reporting so no hardware is initialised. Mouse::Mouse(void) { return; } // Destructor - only ever called when the class is used for version reporting. Mouse::~Mouse(void) { return; }