diff --git a/Debug.h b/Debug.h new file mode 100644 index 0000000..06ed954 --- /dev/null +++ b/Debug.h @@ -0,0 +1,47 @@ +#pragma once + +#ifndef _DEBUG_h +#define _DEBUG_h +//DEBUG printing +#define REMOTE_DEBUGGING_ENABLED +#define DEBUGGING_ENABLED + +#ifdef DEBUGGING_ENABLED + +#ifdef REMOTE_DEBUGGING_ENABLED +#include //https://github.com/JoaoLopesF/RemoteDebug +extern RemoteDebug Debug; + +#define debugPrintf(fmt, ...) \ + Debug.isRunning() ? Debug.printf( fmt, __VA_ARGS__) : Serial.printf( fmt, __VA_ARGS__) + +#define debugPrint(prnt) \ + Debug.isRunning() ? Debug.print(prnt) : Serial.print(prnt) + +#define debugPrintBase(prnt, base) \ + Debug.isRunning() ? Debug.print(prnt, base) : Serial.print(prnt, base) + +#define debugPrintln(prnt) \ + Debug.isRunning() ? Debug.println(prnt) : Serial.println(prnt) +#else + +#define debugPrintf(fmt, ...) \ + Serial.printf( fmt, __VA_ARGS__) + +#define debugPrint(prnt) \ + Serial.print(prnt) + +#define debugPrintBase(prnt) \ + Serial.print(prnt,base) + +#define debugPrintln(prnt) \ + Serial.println(prnt) + +#endif +#else +#define debugPrintf(fmt, ...) +#define debugPrint(prnt) +#define debugPrintBase(prnt, base) +#define debugPrintln(prnt) +#endif // DEBUGGING_ENABLED +#endif diff --git a/GoodWeCommunicator.cpp b/GoodWeCommunicator.cpp index 2527f46..b3e68d1 100644 --- a/GoodWeCommunicator.cpp +++ b/GoodWeCommunicator.cpp @@ -1,19 +1,19 @@ #include "GoodWeCommunicator.h" -GoodWeCommunicator::GoodWeCommunicator(SettingsManager* settingsMan, bool inDebug) +GoodWeCommunicator::GoodWeCommunicator(SettingsManager* settingsMan) { settingsManager = settingsMan; - debugMode = inDebug; } void GoodWeCommunicator::start() { auto settings = settingsManager->GetSettings(); //create the software serial on the custom pins so we can use the hardware serial for debug comms. - goodweSerial = new SoftwareSerial(); // (RX, TX. inverted, buffer) + goodweSerial = new SoftwareSerial52(); //start the software serial with the params (buffersize is larger than default, that's why we cant ue the constructor) goodweSerial->begin(9600, SWSERIAL_8N1, settings->RS485Rx, settings->RS485Tx, false, BufferSize); //inverter fixed baud rate + //goodweSerial->enableIntTx(false); inverters.clear(); //set the fixed part of our buffer headerBuffer[0] = 0xAA; @@ -28,7 +28,7 @@ void GoodWeCommunicator::start() delay(1); } - Serial.println("GoodWe Communicator started."); + debugPrintln("GoodWe Communicator started."); } void GoodWeCommunicator::stop() @@ -40,61 +40,65 @@ void GoodWeCommunicator::stop() int GoodWeCommunicator::sendData(char address, char controlCode, char functionCode, char dataLength, char* data) { - if (debugMode) - Serial.write("Sending data to inverter(s): "); + debugPrint("Sending data to inverter(s)."); + + //need to send out the crc which is the addition of all previous values. Calculate the address first + int16_t crc = 0; + //send the header first headerBuffer[3] = address; headerBuffer[4] = controlCode; headerBuffer[5] = functionCode; headerBuffer[6] = dataLength; - goodweSerial->write(headerBuffer, 7); - //check if we need to write the data part and send it. - if (dataLength) - goodweSerial->write(data, dataLength); - //need to send out the crc which is the addition of all previous values. - int16_t crc = 0; for (int cnt = 0; cnt < 7; cnt++) - { - if (debugMode) - debugPrintHex(headerBuffer[cnt]); crc += headerBuffer[cnt]; - } for (int cnt = 0; cnt < dataLength; cnt++) - { - if (debugMode) - debugPrintHex(data[cnt]); crc += data[cnt]; - } //write out the high and low auto high = (crc >> 8) & 0xff; auto low = crc & 0xff; + + + goodweSerial->write(headerBuffer, 7); + //check if we need to write the data part and send it. + if (dataLength) + goodweSerial->write(data, dataLength); + + //now log the message (if needed) goodweSerial->write(high); goodweSerial->write(low); - if (debugMode) - { - Serial.print("CRC high/low: "); - debugPrintHex(high); - debugPrintHex(low); - Serial.println("."); - } + + //log everything too + debugPrintln("Sent data to inverter(s):"); + + for (int cnt = 0; cnt < 7; cnt++) + debugPrintHex(headerBuffer[cnt]); + + for (int cnt = 0; cnt < dataLength; cnt++) + debugPrintHex(data[cnt]); + + + debugPrint("CRC high/low: "); + debugPrintHex(high); + debugPrintHex(low); + debugPrintln("."); return 7 + dataLength + 2; //header, data, crc } void GoodWeCommunicator::debugPrintHex(char bt) { - Serial.print("0x"); - Serial.print(bt, HEX); - Serial.print(" "); + debugPrint("0x"); + debugPrintBase(bt, HEX); + debugPrint(" "); } void GoodWeCommunicator::sendDiscovery() { //send out discovery for unregistered devices. - if (debugMode) - Serial.println("Sending discovery"); + debugPrintln("Sending discovery"); sendData(0x7F, 0x00, 0x00, 0x00, nullptr); } @@ -107,12 +111,10 @@ void GoodWeCommunicator::checkOfflineInverters() if (inverters[index].isOnline && !newOnline) { //check if inverter timed out - if (debugMode) - { - Serial.print("Marking inverter @ address: "); - Serial.print((short)inverters[index].address); - Serial.println("offline."); - } + + debugPrint("Marking inverter @ address: "); + debugPrint((short)inverters[index].address); + debugPrintln("offline."); sendRemoveRegistration(inverters[index].address); //send in case the inverter thinks we are online inverters[index].isOnline = inverters[index].addressConfirmed = false; @@ -194,7 +196,7 @@ void GoodWeCommunicator::checkIncomingData() { //there is an open packet timeout. startPacketReceived = false; //wait for start packet again - Serial.println("Comms timeout."); + debugPrintln("Comms timeout."); } } void GoodWeCommunicator::parseIncomingData(char incomingDataLength) // @@ -202,17 +204,15 @@ void GoodWeCommunicator::parseIncomingData(char incomingDataLength) // //first check the crc //Data always start without the start bytes of 0xAA 0x55 //incomingDataLength also has the crc data in it - if (debugMode) - { - Serial.print("Parsing incoming data with length: "); - debugPrintHex(incomingDataLength); - Serial.print(". "); - debugPrintHex(0xAA); - debugPrintHex(0x55); - for (char cnt = 0; cnt < incomingDataLength; cnt++) - debugPrintHex(inputBuffer[cnt]); - Serial.println("."); - } + + debugPrint("Parsing incoming data with length: "); + debugPrintHex(incomingDataLength); + debugPrint(". "); + debugPrintHex(0xAA); + debugPrintHex(0x55); + for (char cnt = 0; cnt < incomingDataLength; cnt++) + debugPrintHex(inputBuffer[cnt]); + debugPrintln("."); int16_t crc = 0xAA + 0x55; for (char cnt = 0; cnt < incomingDataLength - 2; cnt++) @@ -221,25 +221,31 @@ void GoodWeCommunicator::parseIncomingData(char incomingDataLength) // auto high = (crc >> 8) & 0xff; auto low = crc & 0xff; - if (debugMode) - { - Serial.print("CRC received: "); - debugPrintHex(inputBuffer[incomingDataLength - 2]); - debugPrintHex(inputBuffer[incomingDataLength - 1]); - Serial.print(", calculated CRC: "); - debugPrintHex(high); - debugPrintHex(low); - Serial.println("."); - } + + debugPrint("CRC received: "); + debugPrintHex(inputBuffer[incomingDataLength - 2]); + debugPrintHex(inputBuffer[incomingDataLength - 1]); + debugPrint(", calculated CRC: "); + debugPrintHex(high); + debugPrintHex(low); + debugPrintln("."); + //match the crc if (!(high == inputBuffer[incomingDataLength - 2] && low == inputBuffer[incomingDataLength - 1])) return; - if (debugMode) - Serial.println("CRC match."); + debugPrintln("CRC match."); //check the contorl code and function code to see what to do if (inputBuffer[2] == 0x00 && inputBuffer[3] == 0x80) - handleRegistration(inputBuffer + 5, 16); + { + if(incomingDataLength > 21) //check if we have enough data + handleRegistration(inputBuffer + 5, 16); //check length + //if (incomingDataLength > 20) //check if we have enough byte to call handle registration + + //else + // debugPrintln("Not enough data for handle registration"); + } + else if (inputBuffer[2] == 0x00 && inputBuffer[3] == 0x81) handleRegistrationConfirmation(inputBuffer[0]); else if (inputBuffer[2] == 0x01 && inputBuffer[3] == 0x81) @@ -258,12 +264,12 @@ void GoodWeCommunicator::handleRegistration(char* serialNumber, char length) //check inverter if (memcmp(inverters[index].serialNumber, serialNumber, 16) == 0) { - Serial.print("Already registered inverter reregistered with address: "); - Serial.println((short)inverters[index].address); + debugPrint("Already registered inverter reregistered with address: "); + debugPrintln((short)inverters[index].address); //found it. Set to unconfirmed and send out the existing address to the inverter inverters[index].addressConfirmed = false; inverters[index].lastSeen = millis(); - sendAllocateRegisterAddress(serialNumber, inverters[index].address); + sendAllocateRegisterAddress(serialNumber,(short) inverters[index].address); return; } } @@ -281,41 +287,34 @@ void GoodWeCommunicator::handleRegistration(char* serialNumber, char length) lastUsedAddress++; newInverter.address = lastUsedAddress; inverters.push_back(newInverter); - if (debugMode) - { - Serial.print("New inverter found. Current # registrations: "); - Serial.println(inverters.size()); - } + + debugPrint("New inverter found. Current # registrations: "); + debugPrintln(inverters.size()); + sendAllocateRegisterAddress(serialNumber, lastUsedAddress); } void GoodWeCommunicator::handleRegistrationConfirmation(char address) { - if (debugMode) - { - Serial.print("Handling registration information for address: "); - Serial.println((short)address); - } + debugPrint("Handling registration information for address: "); + debugPrintln((short)address); + //lookup the inverter and set it to confirmed auto inverter = getInverterInfoByAddress(address); if (inverter) { - if (debugMode) - Serial.println("Inverter information found in list of inverters."); + debugPrintln("Inverter information found in list of inverters."); inverter->addressConfirmed = true; inverter->isOnline = false; //inverter is online, but we first need to get its information inverter->lastSeen = millis(); } else { - if (debugMode) - { - Serial.print("Error. Could not find the inverter with address: "); - Serial.println((short)address); - Serial.print("Current # registrations: "); - Serial.println(inverters.size()); - } + debugPrint("Error. Could not find the inverter with address: "); + debugPrintln((short)address); + debugPrint("Current # registrations: "); + debugPrintln(inverters.size()); } //get the information straight away askInverterForInformation(address); @@ -379,17 +378,18 @@ void GoodWeCommunicator::askAllInvertersForInformation() askInverterForInformation(inverters[index].address); else { - if (debugMode) - { - Serial.print("Not asking inverter with address: "); - Serial.print((short)inverters[index].address); - Serial.print(" for information. Addressconfirmed: "); - Serial.print((short)inverters[index].addressConfirmed); - Serial.print(", isOnline: "); - Serial.print((short)inverters[index].isOnline); - Serial.println("."); - } + + debugPrint("Not asking inverter with address: "); + debugPrint((short)inverters[index].address); + debugPrint(" for information. Addressconfirmed: "); + debugPrint((short)inverters[index].addressConfirmed); + debugPrint(", isOnline: "); + debugPrint((short)inverters[index].isOnline); + debugPrintln("."); + } + + yield(); } } @@ -411,16 +411,15 @@ GoodWeCommunicator::GoodweInverterInformation* GoodWeCommunicator::getInverterIn void GoodWeCommunicator::sendAllocateRegisterAddress(char* serialNumber, char address) { - if (debugMode) - { - Serial.print("SendAllocateRegisterAddress address: "); - Serial.println((short)address); - } + + debugPrint("SendAllocateRegisterAddress address: "); + debugPrintln((short)address); + //create our registrationpacket with serialnumber and address and send it over char RegisterData[17]; memcpy(RegisterData, serialNumber, 16); - RegisterData[16] = address; + RegisterData[16] = (short)address; //need to send alloc msg sendData(0x7F, 0x00, 0x01, 17, RegisterData); } @@ -430,6 +429,7 @@ void GoodWeCommunicator::sendRemoveRegistration(char address) //send out the remove address to the inverter. If the inverter is still connected it will reconnect after discovery sendData(address, 0x00, 0x02, 0, nullptr); } + void GoodWeCommunicator::handle() { //always check for incoming data @@ -439,19 +439,17 @@ void GoodWeCommunicator::handle() checkOfflineInverters(); //discovery every 10 secs. - if (millis() - lastDiscoverySent >= DISCOVERY_INTERVAL) + if (millis() - lastDiscoverySent >= (inverters.size() ? DISCOVERY_WITH_ACTIVE_INVERTERS_INTERVAL : DISCOVERY_NO_INVERTERS_INTERVAL)) { sendDiscovery(); lastDiscoverySent = millis(); } - - //ask for info update every second - if (millis() - lastInfoUpdateSent >= 1000) + else if (millis() - lastInfoUpdateSent >= INFO_INTERVAL) { askAllInvertersForInformation(); lastInfoUpdateSent = millis(); } - checkIncomingData(); + checkIncomingData(); //check again } diff --git a/GoodWeCommunicator.h b/GoodWeCommunicator.h index 835a20b..7ae73f1 100644 --- a/GoodWeCommunicator.h +++ b/GoodWeCommunicator.h @@ -1,15 +1,17 @@ #pragma once #include -#include +#include "SoftwareSerial52.h" #include #include "SettingsManager.h" #include "TimeLib.h" +#include "Debug.h" #define GOODWE_COMMS_ADDRES 0xAB #define PACKET_TIMEOUT 5000 //5 sec packet timeout #define OFFLINE_TIMEOUT 30000 //30 seconds no data -> inverter offline -#define DISCOVERY_INTERVAL 10000 //10 secs between discovery -#define INFO_INTERVAL 1000 //get inverter info every second +#define DISCOVERY_NO_INVERTERS_INTERVAL 10000 //10 secs between discovery if not found +#define DISCOVERY_WITH_ACTIVE_INVERTERS_INTERVAL 300000 //5 minutes if found +#define INFO_INTERVAL 10000 //get inverter info every ten seconds class GoodWeCommunicator { @@ -56,7 +58,7 @@ class GoodWeCommunicator float eDay = 0.0; }; - GoodWeCommunicator(SettingsManager * settingsManager, bool debugMode = false); + GoodWeCommunicator(SettingsManager * settingsManager); void start(); void stop(); void handle(); @@ -67,16 +69,13 @@ class GoodWeCommunicator private: - static const int BufferSize = 96; // largest packet is 67 bytes long. Extra for receiving with sliding window - SoftwareSerial * goodweSerial; + static const int BufferSize = 256; // largest packet is 67 bytes long. Extra for receiving with sliding window + SoftwareSerial52* goodweSerial; SettingsManager * settingsManager; char headerBuffer[7]; char inputBuffer[BufferSize]; char outputBuffer[BufferSize]; - - bool debugMode; - unsigned long lastReceived = 0; //timeout detection bool startPacketReceived = false; //start packet marker char lastReceivedByte = 0; //packet start consist of 2 bytes to test. This holds the previous byte diff --git a/GoodWeLogger.ino b/GoodWeLogger.ino index cc74ac2..7686bb7 100644 --- a/GoodWeLogger.ino +++ b/GoodWeLogger.ino @@ -10,17 +10,21 @@ #include "MQTTPublisher.h" #include "PVOutputPublisher.h" #include "Settings.h" //change and then rename Settings.example.h to Settings.h to compile - +#include "Debug.h" SettingsManager settingsManager; -GoodWeCommunicator goodweComms(&settingsManager, false); -MQTTPublisher mqqtPublisher(&settingsManager, &goodweComms, false); -PVOutputPublisher pvoutputPublisher(&settingsManager, &goodweComms, false); +GoodWeCommunicator goodweComms(&settingsManager); +MQTTPublisher mqqtPublisher(&settingsManager, &goodweComms); +PVOutputPublisher pvoutputPublisher(&settingsManager, &goodweComms); WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP, NTP_SERVER); bool validTimeSet = false; int reconnectCounter = 0; +#ifdef REMOTE_DEBUGGING_ENABLED +RemoteDebug Debug; +#endif + void setup() { //debug settings @@ -86,6 +90,12 @@ void setup() Serial.println("IP address: "); Serial.println(WiFi.localIP()); +#ifdef REMOTE_DEBUGGING_ENABLED + Debug.begin("GoodweLogger"); + Debug.setResetCmdEnabled(true); + Debug.setCallBackNewClient(&RemoteDebugClientConnected); +#endif + //ntp client goodweComms.start(); mqqtPublisher.start(); @@ -93,6 +103,16 @@ void setup() timeClient.setTimeOffset(settings->timezone * 60 * 60); } + +#ifdef REMOTE_DEBUGGING_ENABLED +void RemoteDebugClientConnected() +{ + //Debug client connected. Print all our info + debugPrintln("===GoodWeLogger remote debug enabled==="); + +} +#endif + bool checkConnectToWifi() { //check for wifi connection @@ -138,7 +158,6 @@ void loop() ESP.restart(); } - ArduinoOTA.handle(); yield(); goodweComms.handle(); @@ -151,4 +170,8 @@ void loop() pvoutputPublisher.handle(); yield(); //prevent wathcdog timeout + +#ifdef REMOTE_DEBUGGING_ENABLED + Debug.handle(); +#endif } diff --git a/GoodWeLogger.vcxproj.filters b/GoodWeLogger.vcxproj.filters index 4107d89..f6675bb 100644 --- a/GoodWeLogger.vcxproj.filters +++ b/GoodWeLogger.vcxproj.filters @@ -36,6 +36,12 @@ Header Files + + Header Files + + + Header Files + @@ -50,5 +56,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/MQTTPublisher.cpp b/MQTTPublisher.cpp index ec7a2ce..39c49ad 100644 --- a/MQTTPublisher.cpp +++ b/MQTTPublisher.cpp @@ -3,12 +3,11 @@ WiFiClient espClient; PubSubClient client(espClient); -MQTTPublisher::MQTTPublisher(SettingsManager* settingsManager, GoodWeCommunicator* goodWe, bool inDebugMode) +MQTTPublisher::MQTTPublisher(SettingsManager* settingsManager, GoodWeCommunicator* goodWe) { randomSeed(micros()); mqttSettingsManager = settingsManager; goodweCommunicator = goodWe; - debugMode = inDebugMode; } MQTTPublisher::~MQTTPublisher() @@ -21,12 +20,11 @@ MQTTPublisher::~MQTTPublisher() bool MQTTPublisher::reconnect() { lastConnectionAttempt = millis(); - if (debugMode) - { - Serial.print("Attempting MQTT connection to server: "); - Serial.print(mqttSettings->mqttHostName); - Serial.println("..."); - } + + debugPrint("Attempting MQTT connection to server: "); + debugPrint(mqttSettings->mqttHostName); + debugPrintln("..."); + // Create a random client ID String clientId = "GoodWeLogger-"; @@ -36,30 +34,29 @@ bool MQTTPublisher::reconnect() bool clientConnected; if (mqttSettings->mqttUserName.length()) { - Serial.println("Using user credientials for authentication."); + debugPrintln("Using user credientials for authentication."); clientConnected = client.connect(clientId.c_str(), mqttSettings->mqttUserName.c_str(), mqttSettings->mqttPassword.c_str()); } else { - Serial.println("Connecting without user credentials."); + debugPrintln("Connecting without user credentials."); clientConnected = client.connect(clientId.c_str()); } if (clientConnected) { - if (debugMode) - Serial.println("connected"); + debugPrintln("connected"); // Once connected, publish an announcement... client.publish("goodwe", "online"); return true; } else { - if (debugMode) - { - Serial.print("failed, rc="); - Serial.print(client.state()); - } + + + debugPrint("failed, rc="); + debugPrint(client.state()); + } return false; @@ -71,10 +68,10 @@ void MQTTPublisher::start() mqttSettings = mqttSettingsManager->GetSettings(); if (mqttSettings->mqttHostName.length() == 0 || mqttSettings->mqttPort == 0) { - Serial.println("MQTT disabled. No hostname or port set."); + debugPrintln("MQTT disabled. No hostname or port set."); return; //not configured } - Serial.println("MQTT enabled. Connecting."); + debugPrintln("MQTT enabled. Connecting."); client.setServer(mqttSettings->mqttHostName.c_str(), mqttSettings->mqttPort); reconnect(); //connect right away isStarted = true; @@ -107,11 +104,10 @@ void MQTTPublisher::handle() for (char cnt = 0; cnt < inverters.size(); cnt++) { auto prependTopic = (String("goodwe/") + String(inverters[cnt].serialNumber)); - if (debugMode) - { - Serial.print("Publishing prepend topic for this inverter is: "); - Serial.println(prependTopic); - } + + debugPrint("Publishing prepend topic for this inverter is: "); + debugPrintln(prependTopic); + //send values when offline or online since the values can be reset when offline if (sendQuick) @@ -163,11 +159,10 @@ void MQTTPublisher::handle() if (sendRegular) lastSentRegularUpdate = millis(); - if (debugMode) - { - Serial.print("MQTT send status: "); - Serial.println(sendOk); - } + + debugPrint("MQTT send status: "); + debugPrintln(sendOk); + } } diff --git a/MQTTPublisher.h b/MQTTPublisher.h index 900ab5a..1237e63 100644 --- a/MQTTPublisher.h +++ b/MQTTPublisher.h @@ -7,6 +7,7 @@ #include #include "PubSubClient.h" #include "WiFiClient.h" +#include "Debug.h" #define RECONNECT_TIMEOUT 15000 @@ -17,7 +18,6 @@ class MQTTPublisher SettingsManager::Settings * mqttSettings; SettingsManager * mqttSettingsManager; GoodWeCommunicator * goodweCommunicator; - bool debugMode; bool isStarted; unsigned long lastConnectionAttempt = 0; //last reconnect @@ -27,7 +27,7 @@ class MQTTPublisher bool publishOnMQTT(String prepend, String topic, String value); bool reconnect(); public: - MQTTPublisher(SettingsManager * settingsManager, GoodWeCommunicator *goodWe, bool inDebugMode = false); + MQTTPublisher(SettingsManager * settingsManager, GoodWeCommunicator *goodWe); ~MQTTPublisher(); void start(); diff --git a/PVOutputPublisher.cpp b/PVOutputPublisher.cpp index ca5f38e..d227259 100644 --- a/PVOutputPublisher.cpp +++ b/PVOutputPublisher.cpp @@ -3,184 +3,181 @@ HTTPClient http; //our web client to perform post with -PVOutputPublisher::PVOutputPublisher(SettingsManager * settingsManager, GoodWeCommunicator * goodWe, bool inDebugMode) -{ - pvOutputSettingsManager = settingsManager; - goodweCommunicator = goodWe; - debugMode = inDebugMode; -} - - -PVOutputPublisher::~PVOutputPublisher() -{ -} - -void PVOutputPublisher::start() -{ - if (!canStart()) +PVOutputPublisher::PVOutputPublisher(SettingsManager* settingsManager, GoodWeCommunicator* goodWe) { - Serial.println("PVOutput is disabled."); - return; + pvOutputSettingsManager = settingsManager; + goodweCommunicator = goodWe; } - if (debugMode) - Serial.println("PVOutputPublisher started."); - lastUpdated = millis(); - isStarted = true; -} - -void PVOutputPublisher::stop() -{ - isStarted = false; -} - -bool PVOutputPublisher::canStart() -{ - pvoutputSettings = pvOutputSettingsManager->GetSettings(); - return (pvoutputSettings->pvoutputApiKey.length() > 0); -} - -bool PVOutputPublisher::getIsStarted() -{ - return isStarted; -} - -void PVOutputPublisher::sendToPvOutput(GoodWeCommunicator::GoodweInverterInformation info) -{ - //need to send out the data to pvouptut> use the avg values for pac, voltage and temp - - http.begin("http://pvoutput.org/service/r2/addstatus.jsp"); //Specify request destination - http.addHeader("X-Pvoutput-Apikey", pvoutputSettings->pvoutputApiKey); - http.addHeader("X-Pvoutput-SystemId", pvoutputSettings->pvoutputSystemId); - http.addHeader("Content-Type", "application/x-www-form-urlencoded"); - - //the inverter only reports eday with a .1 kWh resolution. This messus up the avg in pvoutput because the max resolution is 1200 Wh - //we now the avg power in the last period so we can calc the new eday and compare it - float eDay = info.eDay * 1000; - if (avgCounter) + + + PVOutputPublisher::~PVOutputPublisher() { - float avgWhPower = (float)(currentPacSum / avgCounter) / (60.0 * 60 * 1000 / (float)(millis() - lastUpdated)); - if (eDay - MAX_EDAY_DIFF < prevEday + avgWhPower && abs(prevEday + avgWhPower - eDay) < MAX_EDAY_DIFF) //when a new day starts the 'abs' part will reset to zero - eDay = prevEday + avgWhPower; } - prevEday = eDay; - //construct our post message - String postMsg = String("d=") + String(year()) + getZeroFilled(month()) + getZeroFilled(day()); - postMsg += String("&t=") + getZeroFilled(hour()) + ":" + getZeroFilled(minute()); - //v1 = total wh today - postMsg += String("&v1=") + String(eDay, 0); //TODO: improve resolution by adding avg power to prev val - - //v2 = Power Generation - if (avgCounter) //no datapoints recorded + + void PVOutputPublisher::start() { - if (debugMode) + if (!canStart()) { - Serial.print("Got some readings to calculate the avg power, temp and voltage. # readings: "); - Serial.print(avgCounter); - Serial.print(", pac sum: "); - Serial.println(currentPacSum); + debugPrintln("PVOutput is disabled."); + return; } - postMsg += String("&v2=") + String(currentPacSum / avgCounter); //improve resolution by adding avg power to prev val + debugPrintln("PVOutputPublisher started."); + lastUpdated = millis(); + isStarted = true; + } - //v3 and v4 are power consumption (maybe doable using mqtt?) - //v5 = temp - postMsg += String("&v5=") + String(currentTempSum / avgCounter, 2); - //v6 = voltage - postMsg += String("&v6=") + String(currentVoltageSum / avgCounter, 2); + void PVOutputPublisher::stop() + { + isStarted = false; } - //v7 = custom 1 = vac1 - postMsg += String("&v7=") + String(info.vac1, 2); - //v8 = custom 2 = iac1 - postMsg += String("&v8=") + String(info.iac1, 2); - //v9 = custom 3 = fac1 - postMsg += String("&v9=") + String(info.fac1, 2); - //v10 = custom 4 = vpv1 - postMsg += String("&v10=") + String(info.vpv1, 2); - //v11 = custom 5 = vpv2 - postMsg += String("&v11=") + String(info.vpv2, 2); - //v12 = custom 6 = errormsg - postMsg += String("&v12=") + String(info.errorMessage); - - int httpCode = http.POST(postMsg); //Send the request - String payload = http.getString(); //Get the response payload - http.end(); - if (debugMode) + bool PVOutputPublisher::canStart() { - Serial.println(postMsg); - Serial.print("Result: "); - Serial.println(httpCode); - Serial.print("Payload: "); - Serial.println(payload); + pvoutputSettings = pvOutputSettingsManager->GetSettings(); + return (pvoutputSettings->pvoutputApiKey.length() > 0); } -} - -String PVOutputPublisher::getZeroFilled(int num) -{ - return num < 10 ? "0" + String(num) : String(num); -} - -void PVOutputPublisher::handle() -{ - if (!isStarted) - return; - //check if time elapsed and we need to send the current values - //lastUpdated. For now we only support one inverter - auto inverters = goodweCommunicator->getInvertersInfo(); - if (inverters.size() > 0) + + bool PVOutputPublisher::getIsStarted() { + return isStarted; + } + + void PVOutputPublisher::sendToPvOutput(GoodWeCommunicator::GoodweInverterInformation info) + { + //need to send out the data to pvouptut> use the avg values for pac, voltage and temp + + http.begin("http://pvoutput.org/service/r2/addstatus.jsp"); //Specify request destination + http.addHeader("X-Pvoutput-Apikey", pvoutputSettings->pvoutputApiKey); + http.addHeader("X-Pvoutput-SystemId", pvoutputSettings->pvoutputSystemId); + http.addHeader("Content-Type", "application/x-www-form-urlencoded"); - //check if we need to send the info to pvoutptut. - //if not check if the pac value changed add it to the current sum so we can calc the average on sending - if (wasOnline && millis() - lastUpdated > pvoutputSettings->pvoutputUpdateInterval) + //the inverter only reports eday with a .1 kWh resolution. This messus up the avg in pvoutput because the max resolution is 1200 Wh + //we now the avg power in the last period so we can calc the new eday and compare it + float eDay = info.eDay * 1000; + if (avgCounter) + { + float avgWhPower = (float)(currentPacSum / avgCounter) / (60.0 * 60 * 1000 / (float)(millis() - lastUpdated)); + if (eDay - MAX_EDAY_DIFF < prevEday + avgWhPower && abs(prevEday + avgWhPower - eDay) < MAX_EDAY_DIFF) //when a new day starts the 'abs' part will reset to zero + eDay = prevEday + avgWhPower; + } + prevEday = eDay; + //construct our post message + String postMsg = String("d=") + String(year()) + getZeroFilled(month()) + getZeroFilled(day()); + postMsg += String("&t=") + getZeroFilled(hour()) + ":" + getZeroFilled(minute()); + //v1 = total wh today + postMsg += String("&v1=") + String(eDay, 0); //TODO: improve resolution by adding avg power to prev val + + //v2 = Power Generation + if (avgCounter) //no datapoints recorded { - //send it out - sendToPvOutput(inverters[0]); - ResetAverage(); - lastUpdated = millis(); - if (!inverters[0].isOnline && wasOnline) - { - //went offline. Data was sent for the last time - wasOnline = false; - //cleaar all avg data first - ResetAverage(); - } + debugPrint("Got some readings to calculate the avg power, temp and voltage. # readings: "); + debugPrint(avgCounter); + debugPrint(", pac sum: "); + debugPrintln(currentPacSum); + + + postMsg += String("&v2=") + String(currentPacSum / avgCounter); //improve resolution by adding avg power to prev val + + //v3 and v4 are power consumption (maybe doable using mqtt?) + //v5 = temp + postMsg += String("&v5=") + String(currentTempSum / avgCounter, 2); + //v6 = voltage + postMsg += String("&v6=") + String(currentVoltageSum / avgCounter, 2); } - else + + //v7 = custom 1 = vac1 + postMsg += String("&v7=") + String(info.vac1, 2); + //v8 = custom 2 = iac1 + postMsg += String("&v8=") + String(info.iac1, 2); + //v9 = custom 3 = fac1 + postMsg += String("&v9=") + String(info.fac1, 2); + //v10 = custom 4 = vpv1 + postMsg += String("&v10=") + String(info.vpv1, 2); + //v11 = custom 5 = vpv2 + postMsg += String("&v11=") + String(info.vpv2, 2); + //v12 = custom 6 = errormsg + postMsg += String("&v12=") + String(info.errorMessage); + + int httpCode = http.POST(postMsg); //Send the request + String payload = http.getString(); //Get the response payload + http.end(); + + debugPrintln(postMsg); + debugPrint("Result: "); + debugPrintln(httpCode); + debugPrint("Payload: "); + debugPrintln(payload); + + } + + String PVOutputPublisher::getZeroFilled(int num) + { + return num < 10 ? "0" + String(num) : String(num); + } + + void PVOutputPublisher::handle() + { + if (!isStarted) + return; + //check if time elapsed and we need to send the current values + //lastUpdated. For now we only support one inverter + auto inverters = goodweCommunicator->getInvertersInfo(); + if (inverters.size() > 0) { - //keep track of when the inverter went offline - if (!wasOnline && inverters[0].isOnline) + + //check if we need to send the info to pvoutptut. + //if not check if the pac value changed add it to the current sum so we can calc the average on sending + if (wasOnline && millis() - lastUpdated > pvoutputSettings->pvoutputUpdateInterval) { - wasOnline = true; - //cleaar all avg data first + //send it out + sendToPvOutput(inverters[0]); ResetAverage(); + lastUpdated = millis(); + + if (!inverters[0].isOnline && wasOnline) + { + //went offline. Data was sent for the last time + wasOnline = false; + //cleaar all avg data first + ResetAverage(); + } } - - //check if inverter info was updated - if (inverters[0].isOnline && (inverters[0].pac != lastPac || inverters[0].vpv1 + inverters[0].vpv2 != lastVoltage || inverters[0].temp != lastTemp)) + else { - //changed. so change the avg counters - lastPac = inverters[0].pac; - lastVoltage = inverters[0].vpv1 + inverters[0].vpv2; - lastTemp = inverters[0].temp; - currentPacSum += lastPac; - currentVoltageSum += lastVoltage; - currentTempSum += lastTemp; - avgCounter += 1; + //keep track of when the inverter went offline + if (!wasOnline && inverters[0].isOnline) + { + wasOnline = true; + //cleaar all avg data first + ResetAverage(); + } + + //check if inverter info was updated + if (inverters[0].isOnline && (inverters[0].pac != lastPac || inverters[0].vpv1 + inverters[0].vpv2 != lastVoltage || inverters[0].temp != lastTemp)) + { + //changed. so change the avg counters + lastPac = inverters[0].pac; + lastVoltage = inverters[0].vpv1 + inverters[0].vpv2; + lastTemp = inverters[0].temp; + currentPacSum += lastPac; + currentVoltageSum += lastVoltage; + currentTempSum += lastTemp; + avgCounter += 1; + } } } } -} - -void PVOutputPublisher::ResetAverage() -{ - //reset counter vals - avgCounter = 0; - currentPacSum = 0; - currentVoltageSum = 0; - currentTempSum = 0; - lastPac = 0; - lastVoltage = 0; - lastTemp = 0; -} + + void PVOutputPublisher::ResetAverage() + { + //reset counter vals + avgCounter = 0; + currentPacSum = 0; + currentVoltageSum = 0; + currentTempSum = 0; + lastPac = 0; + lastVoltage = 0; + lastTemp = 0; + } diff --git a/PVOutputPublisher.h b/PVOutputPublisher.h index d801ef8..750e01d 100644 --- a/PVOutputPublisher.h +++ b/PVOutputPublisher.h @@ -5,13 +5,14 @@ #include "SettingsManager.h" #include "GoodWeCommunicator.h" #include "ESP8266HTTPClient.h" +#include "Debug.h" #define MAX_EDAY_DIFF 100.0f class PVOutputPublisher { public: - PVOutputPublisher(SettingsManager * settingsManager, GoodWeCommunicator *goodWe, bool inDebugMode = false); + PVOutputPublisher(SettingsManager * settingsManager, GoodWeCommunicator *goodWe); ~PVOutputPublisher(); void start(); @@ -28,7 +29,6 @@ class PVOutputPublisher SettingsManager::Settings * pvoutputSettings; SettingsManager * pvOutputSettingsManager; GoodWeCommunicator * goodweCommunicator; - bool debugMode; unsigned long lastUpdated; bool isStarted = false; unsigned long currentPacSum = 0; diff --git a/Settings.example.h b/Settings.example.h index 713c711..41810ab 100644 --- a/Settings.example.h +++ b/Settings.example.h @@ -17,10 +17,10 @@ #define MQTT_PASSWORD "" //update interval for fast changing values in milliseconds for mqtt -#define MQTT_QUICK_UPDATE_INTERVAL 1000 +#define MQTT_QUICK_UPDATE_INTERVAL 10000 //update interval for slow changing values in milliseconds for mqtt -#define MQTT_REGULAR_UPDATE_INTERVAL 10000 +#define MQTT_REGULAR_UPDATE_INTERVAL 60000 //set to your pvoutput api key (must have write rights). Leave empty to disable pvoutput publishing #define PVOUTPUT_API_KEY "" @@ -50,4 +50,10 @@ #define WIFI_CONNECT_TIMEOUT 30*1000 //Inverter data reset after 11 minutes (inverter reconnect timeout is 10 minutes, 1 minute extra to avoid too quick reset) -#define INVERTER_OFFLINE_RESET_VALUES_TIMEOUT 11*60*1000 \ No newline at end of file +#define INVERTER_OFFLINE_RESET_VALUES_TIMEOUT 11*60*1000 + +//Enable debbugging through serial or remote +#define DEBUGGING_ENABLED true + +//Enable telnet/remote debugging? +#define REMOTE_DEBUGGING_ENABLED true \ No newline at end of file diff --git a/SoftwareSerial52.cpp b/SoftwareSerial52.cpp new file mode 100644 index 0000000..d24a75a --- /dev/null +++ b/SoftwareSerial52.cpp @@ -0,0 +1,419 @@ +/* + +SoftwareSerial52.cpp - Implementation of the Arduino software serial for ESP8266/ESP32. +Copyright (c) 2015-2016 Peter Lerup. All rights reserved. +Copyright (c) 2018-2019 Dirk O. Kaar. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "SoftwareSerial52.h" +#include + +#ifdef ESP32 +#define xt_rsil(a) (a) +#define xt_wsr_ps(a) +#endif + +constexpr uint8_t BYTE_ALL_BITS_SET = ~static_cast(0); + +SoftwareSerial52::SoftwareSerial52() { + m_isrOverflow = false; +} + +SoftwareSerial52::SoftwareSerial52(int8_t rxPin, int8_t txPin) +{ + m_isrOverflow = false; + m_rxPin = rxPin; + m_txPin = txPin; +} + + +SoftwareSerial52::~SoftwareSerial52() { + end(); +} + +bool SoftwareSerial52::isValidGPIOpin(int8_t pin) { +#if defined(ESP8266) + return (pin >= 0 && pin <= 5) || (pin >= 12 && pin <= 15); +#elif defined(ESP32) + return pin == 0 || pin == 2 || (pin >= 4 && pin <= 5) || (pin >= 12 && pin <= 19) || + (pin >= 21 && pin <= 23) || (pin >= 25 && pin <= 27) || (pin >= 32 && pin <= 35); +#else + return true; +#endif +} + +void SoftwareSerial52::begin(uint32_t baud, SoftwareSerial52Config config, + int8_t rxPin, int8_t txPin, + bool invert, int bufCapacity, int isrBufCapacity) { + if (-1 != rxPin) m_rxPin = rxPin; + if (-1 != txPin) m_txPin = txPin; + m_oneWire = (m_rxPin == m_txPin); + m_invert = invert; + if (isValidGPIOpin(m_rxPin)) { + std::unique_ptr > buffer(new circular_queue((bufCapacity > 0) ? bufCapacity : 64)); + m_buffer = move(buffer); + std::unique_ptr > isrBuffer(new circular_queue((isrBufCapacity > 0) ? isrBufCapacity : (sizeof(uint8_t) * 8 + 2) * bufCapacity)); + m_isrBuffer = move(isrBuffer); + if (m_buffer != 0 && m_isrBuffer != 0) { + m_rxValid = true; + pinMode(m_rxPin, INPUT_PULLUP); + } + } + if (isValidGPIOpin(m_txPin) +#ifdef ESP8266 + || ((m_txPin == 16) && !m_oneWire)) { +#else + ) { +#endif + m_txValid = true; + if (!m_oneWire) { + pinMode(m_txPin, OUTPUT); + digitalWrite(m_txPin, !m_invert); + } + } + + m_dataBits = 5 + config; + m_bit_us = (1000000 + baud / 2) / baud; + m_bitCycles = (ESP.getCpuFreqMHz() * 1000000 + baud / 2) / baud; + m_intTxEnabled = true; + if (!m_rxEnabled) { enableRx(true); } +} + +void SoftwareSerial52::end() +{ + enableRx(false); + m_txValid = false; + if (m_buffer) { + m_buffer.reset(); + } + if (m_isrBuffer) { + m_isrBuffer.reset(); + } +} + +uint32_t SoftwareSerial52::baudRate() { + return ESP.getCpuFreqMHz() * 1000000 / m_bitCycles; +} + +void SoftwareSerial52::setTransmitEnablePin(int8_t txEnablePin) { + if (isValidGPIOpin(txEnablePin)) { + m_txEnableValid = true; + m_txEnablePin = txEnablePin; + pinMode(m_txEnablePin, OUTPUT); + digitalWrite(m_txEnablePin, LOW); + } + else { + m_txEnableValid = false; + } +} + +void SoftwareSerial52::enableIntTx(bool on) { + m_intTxEnabled = on; +} + +void SoftwareSerial52::enableTx(bool on) { + if (m_txValid && m_oneWire) { + if (on) { + enableRx(false); + pinMode(m_txPin, OUTPUT); + digitalWrite(m_txPin, !m_invert); + } + else { + pinMode(m_rxPin, INPUT_PULLUP); + enableRx(true); + } + } +} + +void SoftwareSerial52::enableRx(bool on) { + if (m_rxValid) { + if (on) { + m_rxCurBit = m_dataBits; + // Init to stop bit level and current cycle + m_isrLastCycle = (ESP.getCycleCount() | 1) ^ m_invert; + if (m_bitCycles >= (ESP.getCpuFreqMHz() * 1000000U) / 74880U) + attachInterruptArg(digitalPinToInterrupt(m_rxPin), reinterpret_cast(rxBitISR), this, CHANGE); + else + attachInterruptArg(digitalPinToInterrupt(m_rxPin), reinterpret_cast(rxBitSyncISR), this, m_invert ? RISING : FALLING); + } + else { + detachInterrupt(digitalPinToInterrupt(m_rxPin)); + } + m_rxEnabled = on; + } +} + +int SoftwareSerial52::read() { + if (!m_rxValid) { return -1; } + if (!m_buffer->available()) { + rxBits(); + if (!m_buffer->available()) { return -1; } + } + return m_buffer->pop(); +} + +size_t SoftwareSerial52::readBytes(uint8_t * buffer, size_t size) { + if (!m_rxValid) { return -1; } + if (0 != (size = m_buffer->pop_n(buffer, size))) return size; + rxBits(); + size = m_buffer->pop_n(buffer, size); + return (size == 0) ? -1 : size; +} + +int SoftwareSerial52::available() { + if (!m_rxValid) { return 0; } + rxBits(); + int avail = m_buffer->available(); + if (!avail) { + optimistic_yield(10000); + } + return avail; +} + +void ICACHE_RAM_ATTR SoftwareSerial52::preciseDelay(bool asyn, uint32_t savedPS) { + if (asyn) + { + if (!m_intTxEnabled) { xt_wsr_ps(savedPS); } + auto expired = ESP.getCycleCount() - m_periodStart; + auto micro_s = expired < m_periodDuration ? (m_periodDuration - expired) / ESP.getCpuFreqMHz() : 0; + delay(micro_s / 1000); + } + while ((ESP.getCycleCount() - m_periodStart) < m_periodDuration) { if (asyn) optimistic_yield(10000); } + if (asyn) + { + resetPeriodStart(); + if (!m_intTxEnabled) { savedPS = xt_rsil(15); } + } +} + +void ICACHE_RAM_ATTR SoftwareSerial52::writePeriod( + uint32_t dutyCycle, uint32_t offCycle, bool withStopBit, uint32_t savedPS) { + preciseDelay(false, savedPS); + if (dutyCycle) { + digitalWrite(m_txPin, HIGH); + m_periodDuration += dutyCycle; + bool asyn = withStopBit && !m_invert; + // Reenable interrupts while delaying to avoid other tasks piling up + if (asyn || offCycle) preciseDelay(asyn, savedPS); + // Disable interrupts again + } + if (offCycle) { + digitalWrite(m_txPin, LOW); + m_periodDuration += offCycle; + bool asyn = withStopBit && m_invert; + // Reenable interrupts while delaying to avoid other tasks piling up + if (asyn) preciseDelay(asyn, savedPS); + // Disable interrupts again + } +} + +size_t SoftwareSerial52::write(uint8_t b) { + return write(&b, 1); +} + +size_t ICACHE_RAM_ATTR SoftwareSerial52::write(const uint8_t * buffer, size_t size) { + if (m_rxValid) { rxBits(); } + if (!m_txValid) { return -1; } + + if (m_txEnableValid) { + digitalWrite(m_txEnablePin, HIGH); + } + // Stop bit : LOW if inverted logic, otherwise HIGH + bool b = !m_invert; + // Force line level on entry + uint32_t dutyCycle = b; + uint32_t offCycle = m_invert; + uint32_t savedPS = 0; + if (!m_intTxEnabled) { + // Disable interrupts in order to get a clean transmit timing + savedPS = xt_rsil(15); + } + resetPeriodStart(); + const uint32_t dataMask = ((1UL << m_dataBits) - 1); + for (size_t cnt = 0; cnt < size; ++cnt, ++buffer) { + bool withStopBit = true; + // push LSB start-data-stop bit pattern into uint32_t + // Stop bit : LOW if inverted logic, otherwise HIGH + uint32_t word = (!m_invert) << m_dataBits; + word |= (m_invert ? ~*buffer : *buffer) & dataMask; + // Start bit : HIGH if inverted logic, otherwise LOW + word <<= 1; + word |= m_invert; + for (int i = 0; i < m_dataBits + 2; ++i) { + bool pb = b; + b = (word >> i) & 1; + if (!pb && b) { + writePeriod(dutyCycle, offCycle, withStopBit, savedPS); + withStopBit = false; + dutyCycle = offCycle = 0; + } + if (b) { + dutyCycle += m_bitCycles; + } + else { + offCycle += m_bitCycles; + } + } + } + writePeriod(dutyCycle, offCycle, true, savedPS); + if (!m_intTxEnabled) { + // restore the interrupt state + xt_wsr_ps(savedPS); + } + if (m_txEnableValid) { + digitalWrite(m_txEnablePin, LOW); + } + return size; +} + +void SoftwareSerial52::flush() { + if (!m_rxValid) { return; } + m_buffer->flush(); +} + +bool SoftwareSerial52::overflow() { + bool res = m_overflow; + m_overflow = false; + return res; +} + +int SoftwareSerial52::peek() { + if (!m_rxValid) { return -1; } + if (!m_buffer->available()) { + rxBits(); + if (!m_buffer->available()) return -1; + } + return m_buffer->peek(); +} + +void SoftwareSerial52::rxBits() { + int isrAvail = m_isrBuffer->available(); +#ifdef ESP8266 + if (m_isrOverflow.load()) { + m_overflow = true; + m_isrOverflow.store(false); + } +#else + if (m_isrOverflow.exchange(false)) { + m_overflow = true; + } +#endif + + // stop bit can go undetected if leading data bits are at same level + // and there was also no next start bit yet, so one byte may be pending. + // low-cost check first + if (!isrAvail && m_rxCurBit >= -1 && m_rxCurBit < m_dataBits) { + uint32_t detectionCycles = (m_dataBits - m_rxCurBit) * m_bitCycles; + if (ESP.getCycleCount() - m_isrLastCycle > detectionCycles) { + // Produce faux stop bit level, prevents start bit maldetection + // cycle's LSB is repurposed for the level bit + rxBits(((m_isrLastCycle + detectionCycles) | 1) ^ m_invert); + } + } + + m_isrBuffer->for_each([this](const uint32_t& isrCycle) { rxBits(isrCycle); }); +} + +void SoftwareSerial52::rxBits(const uint32_t & isrCycle) { + bool level = (m_isrLastCycle & 1) ^ m_invert; + + // error introduced by edge value in LSB of isrCycle is negligible + int32_t cycles = isrCycle - m_isrLastCycle; + m_isrLastCycle = isrCycle; + + uint8_t bits = cycles / m_bitCycles; + if (cycles % m_bitCycles > (m_bitCycles >> 1)) ++bits; + while (bits > 0) { + // start bit detection + if (m_rxCurBit >= m_dataBits) { + // leading edge of start bit + if (level) break; + m_rxCurBit = -1; + --bits; + continue; + } + // data bits + if (m_rxCurBit >= -1 && m_rxCurBit < (m_dataBits - 1)) { + int8_t dataBits = min(bits, static_cast(m_dataBits - m_rxCurBit - 1)); + m_rxCurBit += dataBits; + bits -= dataBits; + m_rxCurByte >>= dataBits; + if (level) { m_rxCurByte |= (BYTE_ALL_BITS_SET << (8 - dataBits)); } + continue; + } + // stop bit + if (m_rxCurBit == (m_dataBits - 1)) { + // Store the received value in the buffer unless we have an overflow + // if not high stop bit level, discard word + if (level) + { + m_buffer->push(m_rxCurByte >> (sizeof(uint8_t) * 8 - m_dataBits)); + } + ++m_rxCurBit; + // reset to 0 is important for masked bit logic + m_rxCurByte = 0; + break; + } + break; + } +} + +void ICACHE_RAM_ATTR SoftwareSerial52::rxBitISR(SoftwareSerial52 * self) { + uint32_t curCycle = ESP.getCycleCount(); + bool level = digitalRead(self->m_rxPin); + + // Store level and cycle in the buffer unless we have an overflow + // cycle's LSB is repurposed for the level bit + if (!self->m_isrBuffer->push((curCycle | 1U) ^ !level)) self->m_isrOverflow.store(true); +} + +void ICACHE_RAM_ATTR SoftwareSerial52::rxBitSyncISR(SoftwareSerial52 * self) { + uint32_t start = ESP.getCycleCount(); + uint32_t wait = self->m_bitCycles - 172U; + + bool level = self->m_invert; + // Store level and cycle in the buffer unless we have an overflow + // cycle's LSB is repurposed for the level bit + if (!self->m_isrBuffer->push(((start + wait) | 1U) ^ !level)) self->m_isrOverflow.store(true); + + for (uint32_t i = 0; i < self->m_dataBits + 1U; ++i) { + while (ESP.getCycleCount() - start < wait) {}; + wait += self->m_bitCycles; + + // Store level and cycle in the buffer unless we have an overflow + // cycle's LSB is repurposed for the level bit + if (digitalRead(self->m_rxPin) != level) + { + if (!self->m_isrBuffer->push(((start + wait) | 1U) ^ level)) self->m_isrOverflow.store(true); + level = !level; + } + } +} + +void SoftwareSerial52::onReceive(std::function handler) { + receiveHandler = handler; +} + +void SoftwareSerial52::perform_work() { + if (!m_rxValid) { return; } + rxBits(); + if (receiveHandler) { + int avail = m_buffer->available(); + if (avail) { receiveHandler(avail); } + } +} diff --git a/SoftwareSerial52.h b/SoftwareSerial52.h new file mode 100644 index 0000000..5c7659c --- /dev/null +++ b/SoftwareSerial52.h @@ -0,0 +1,167 @@ +/* +SoftwareSerial52.h + +SoftwareSerial52.cpp - Implementation of the Arduino software serial for ESP8266/ESP32. +Copyright (c) 2015-2016 Peter Lerup. All rights reserved. +Copyright (c) 2018-2019 Dirk O. Kaar. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef __SoftwareSerial52_h +#define __SoftwareSerial52_h + +#include "circular_queue/circular_queue.h" +#include +#include + +enum SoftwareSerial52Config { + SWSERIAL_5N1 = 0, + SWSERIAL_6N1, + SWSERIAL_7N1, + SWSERIAL_8N1, +}; + +/// This class is compatible with the corresponding AVR one, however, +/// the constructor takes no arguments, for compatibility with the +/// HardwareSerial class. +/// Instead, the begin() function handles pin assignments and logic inversion. +/// It also has optional input buffer capacity arguments for byte buffer and ISR bit buffer. +/// Bitrates up to at least 115200 can be used. +class SoftwareSerial52 : public Stream { +public: + SoftwareSerial52(); + /// Ctor to set defaults for pins. + /// @param rxPin the GPIO pin used for RX + /// @param txPin -1 for onewire protocol, GPIO pin used for twowire TX + SoftwareSerial52(int8_t rxPin, int8_t txPin = -1); + SoftwareSerial52(const SoftwareSerial52&) = delete; + SoftwareSerial52& operator= (const SoftwareSerial52&) = delete; + virtual ~SoftwareSerial52(); + /// Configure the SoftwareSerial52 object for use. + /// @param baud the TX/RX bitrate + /// @param config sets databits, parity, and stop bit count + /// @param rxPin -1 or default: either no RX pin, or keeps the rxPin set in the ctor + /// @param txPin -1 or default: either no TX pin (onewire), or keeps the txPin set in the ctor + /// @param invert true: uses invert line level logic + /// @param bufCapacity the capacity for the received bytes buffer + /// @param isrBufCapacity 0: derived from bufCapacity. The capacity of the internal asynchronous + /// bit receive buffer, a suggested size is bufCapacity times the sum of + /// start, data, parity and stop bit count. + void begin(uint32_t baud, SoftwareSerial52Config config = SWSERIAL_8N1, + int8_t rxPin = -1, int8_t txPin = -1, + bool invert = false, int bufCapacity = 64, int isrBufCapacity = 0); + uint32_t baudRate(); + /// Transmit control pin. + void setTransmitEnablePin(int8_t txEnablePin); + /// Enable or disable interrupts during tx. + void enableIntTx(bool on); + + bool overflow(); + + int available() override; + int availableForWrite() { + if (!m_txValid) return 0; + return 1; + } + int peek() override; + int read() override; + /// The readBytes functions are non-waiting, there is no timeout. + size_t readBytes(uint8_t* buffer, size_t size) override; + /// The readBytes functions are non-waiting, there is no timeout. + size_t readBytes(char* buffer, size_t size) override { + return readBytes(reinterpret_cast(buffer), size); + } + void flush() override; + size_t write(uint8_t byte) override; + size_t write(const uint8_t* buffer, size_t size) override; + size_t write(const char* buffer, size_t size) { + return write(reinterpret_cast(buffer), size); + } + operator bool() const { return m_rxValid || m_txValid; } + + /// Disable or enable interrupts on the rx pin. + void enableRx(bool on); + /// One wire control. + void enableTx(bool on); + + // AVR compatibility methods. + bool listen() { enableRx(true); return true; } + void end(); + bool isListening() { return m_rxEnabled; } + bool stopListening() { enableRx(false); return true; } + + /// Set an event handler for received data. + void onReceive(std::function handler); + + /// Run the internal processing and event engine. Can be iteratively called + /// from loop, or otherwise scheduled. + void perform_work(); + + using Print::write; + +private: + void resetPeriodStart() + { + m_periodDuration = 0; + m_periodStart = ESP.getCycleCount(); + } + // If asyn, it's legal to exceed the deadline, for instance, + // by enabling interrupts. + void preciseDelay(bool asyn, uint32_t savedPS); + // If withStopBit is set, either cycle contains a stop bit. + // If dutyCycle == 0, the level is not forced to HIGH. + // If offCycle == 0, the level remains unchanged from dutyCycle. + void writePeriod( + uint32_t dutyCycle, uint32_t offCycle, bool withStopBit, uint32_t savedPS); + bool isValidGPIOpin(int8_t pin); + /* check m_rxValid that calling is safe */ + void rxBits(); + void rxBits(const uint32_t& isrCycle); + + static void rxBitISR(SoftwareSerial52* self); + static void rxBitSyncISR(SoftwareSerial52* self); + + // Member variables + bool m_oneWire; + int8_t m_rxPin = -1; + int8_t m_txPin = -1; + int8_t m_txEnablePin = -1; + bool m_rxValid = false; + bool m_rxEnabled = false; + bool m_txValid = false; + bool m_txEnableValid = false; + bool m_invert; + bool m_overflow = false; + uint8_t m_dataBits; + uint32_t m_bit_us; + uint32_t m_bitCycles; + uint32_t m_periodStart; + uint32_t m_periodDuration; + bool m_intTxEnabled; + std::unique_ptr > m_buffer; + // the ISR stores the relative bit times in the buffer. The inversion corrected level is used as sign bit (2's complement): + // 1 = positive including 0, 0 = negative. + std::unique_ptr > m_isrBuffer; + std::atomic m_isrOverflow; + uint32_t m_isrLastCycle; + int8_t m_rxCurBit; // 0 - 7: data bits. -1: start bit. 8: stop bit. + uint8_t m_rxCurByte = 0; + + std::function receiveHandler; +}; + +#endif // __SoftwareSerial52_h diff --git a/circular_queue/Delegate.h b/circular_queue/Delegate.h new file mode 100644 index 0000000..4ec3616 --- /dev/null +++ b/circular_queue/Delegate.h @@ -0,0 +1,2130 @@ +/* +Delegate.h - An efficient interchangeable C function ptr and C++ std::function delegate +Copyright (c) 2019 Dirk O. Kaar. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __Delegate_h +#define __Delegate_h + +#if defined(ESP8266) +#include +#elif defined(ESP32) +#include +#else +#define ICACHE_RAM_ATTR +#define IRAM_ATTR +#endif + +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) +#include +#include +#else +#include "circular_queue/ghostl.h" +#endif + +namespace +{ + + template + R IRAM_ATTR vPtrToFunPtrExec(void* fn, P... args) + { + using target_type = R(P...); + return reinterpret_cast(fn)(std::forward(args...)); + } + +} + +namespace delegate +{ + namespace detail + { + +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + template + class DelegatePImpl { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A, P...); + using FunVPPtr = R(*)(void*, P...); + using FunctionType = std::function; + public: + DelegatePImpl() + { + kind = FP; + fn = nullptr; + } + + DelegatePImpl(std::nullptr_t) + { + kind = FP; + fn = nullptr; + } + + ~DelegatePImpl() + { + if (FUNC == kind) + functional.~FunctionType(); + else if (FPA == kind) + obj.~A(); + } + + DelegatePImpl(const DelegatePImpl& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(del.functional); + } + else if (FPA == del.kind) + { + fnA = del.fnA; + new (&obj) A(del.obj); + } + else + { + fn = del.fn; + } + } + + DelegatePImpl(DelegatePImpl&& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(std::move(del.functional)); + } + else if (FPA == del.kind) + { + fnA = del.fnA; + new (&obj) A(std::move(del.obj)); + } + else + { + fn = del.fn; + } + } + + DelegatePImpl(FunAPtr fnA, const A& obj) + { + kind = FPA; + DelegatePImpl::fnA = fnA; + new (&this->obj) A(obj); + } + + DelegatePImpl(FunAPtr fnA, A&& obj) + { + kind = FPA; + DelegatePImpl::fnA = fnA; + new (&this->obj) A(std::move(obj)); + } + + DelegatePImpl(FunPtr fn) + { + kind = FP; + DelegatePImpl::fn = fn; + } + + template DelegatePImpl(F functional) + { + kind = FUNC; + new (&this->functional) FunctionType(std::forward(functional)); + } + + DelegatePImpl& operator=(const DelegatePImpl& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + if (FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + else if (FPA == del.kind) + { + new (&obj) A; + } + kind = del.kind; + } + if (FUNC == del.kind) + { + functional = del.functional; + } + else if (FPA == del.kind) + { + fnA = del.fnA; + obj = del.obj; + } + else + { + fn = del.fn; + } + return *this; + } + + DelegatePImpl& operator=(DelegatePImpl&& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + if (FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + else if (FPA == del.kind) + { + new (&obj) A; + } + kind = del.kind; + } + if (FUNC == del.kind) + { + functional = std::move(del.functional); + } + else if (FPA == del.kind) + { + fnA = del.fnA; + obj = std::move(del.obj); + } + else + { + fn = del.fn; + } + return *this; + } + + DelegatePImpl& operator=(FunPtr fn) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + kind = FP; + this->fn = fn; + return *this; + } + + DelegatePImpl& IRAM_ATTR operator=(std::nullptr_t) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + kind = FP; + fn = nullptr; + return *this; + } + + operator bool() const + { + if (FP == kind) + { + return fn; + } + else if (FPA == kind) + { + return fnA; + } + else + { + return functional ? true : false; + } + } + + static R IRAM_ATTR vPtrToFunAPtrExec(void* self, P... args) + { + return static_cast(self)->fnA( + static_cast(self)->obj, + std::forward(args...)); + }; + + operator FunVPPtr() const + { + if (FP == kind) + { + return vPtrToFunPtrExec; + } + else if (FPA == kind) + { + return vPtrToFunAPtrExec; + } + else + { + return [](void* self, P... args) -> R + { + return static_cast(self)->functional(std::forward(args...)); + }; + } + } + + void* arg() const + { + if (FP == kind) + { + return reinterpret_cast(fn); + } + else + { + return const_cast(this); + } + } + + operator FunctionType() const + { + if (FP == kind) + { + return fn; + } + else if (FPA == kind) + { + return [this](P... args) { return fnA(obj, std::forward(args...)); }; + } + else + { + return functional; + } + } + + R IRAM_ATTR operator()(P... args) const + { + if (FP == kind) + { + return fn(std::forward(args...)); + } + else if (FPA == kind) + { + return fnA(obj, std::forward(args...)); + } + else + { + return functional(std::forward(args...)); + } + } + + protected: + enum { FUNC, FP, FPA } kind; + union { + FunctionType functional; + FunPtr fn; + struct { + FunAPtr fnA; + A obj; + }; + }; + }; +#else + template + class DelegatePImpl { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A, P...); + using FunVPPtr = R(*)(void*, P...); + public: + DelegatePImpl() + { + kind = FP; + fn = nullptr; + } + + DelegatePImpl(std::nullptr_t) + { + kind = FP; + fn = nullptr; + } + + DelegatePImpl(const DelegatePImpl& del) + { + kind = del.kind; + if (FPA == del.kind) + { + fnA = del.fnA; + obj = del.obj; + } + else + { + fn = del.fn; + } + } + + DelegatePImpl(DelegatePImpl&& del) + { + kind = del.kind; + if (FPA == del.kind) + { + fnA = del.fnA; + obj = std::move(del.obj); + } + else + { + fn = del.fn; + } + } + + DelegatePImpl(FunAPtr fnA, const A& obj) + { + kind = FPA; + DelegatePImpl::fnA = fnA; + this->obj = obj; + } + + DelegatePImpl(FunAPtr fnA, A&& obj) + { + kind = FPA; + DelegatePImpl::fnA = fnA; + this->obj = std::move(obj); + } + + DelegatePImpl(FunPtr fn) + { + kind = FP; + DelegatePImpl::fn = fn; + } + + template DelegatePImpl(F fn) + { + kind = FP; + DelegatePImpl::fn = std::forward(fn); + } + + DelegatePImpl& operator=(const DelegatePImpl& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FPA == kind) + { + obj = {}; + } + kind = del.kind; + } + if (FPA == del.kind) + { + fnA = del.fnA; + obj = del.obj; + } + else + { + fn = del.fn; + } + return *this; + } + + DelegatePImpl& operator=(DelegatePImpl&& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FPA == kind) + { + obj = {}; + } + kind = del.kind; + } + if (FPA == del.kind) + { + fnA = del.fnA; + obj = std::move(del.obj); + } + else + { + fn = del.fn; + } + return *this; + } + + DelegatePImpl& operator=(FunPtr fn) + { + if (FPA == kind) + { + obj = {}; + } + kind = FP; + this->fn = fn; + return *this; + } + + DelegatePImpl& IRAM_ATTR operator=(std::nullptr_t) + { + if (FPA == kind) + { + obj = {}; + } + kind = FP; + fn = nullptr; + return *this; + } + + operator bool() const + { + if (FP == kind) + { + return fn; + } + else + { + return fnA; + } + } + + static R IRAM_ATTR vPtrToFunAPtrExec(void* self, P... args) + { + return static_cast(self)->fnA( + static_cast(self)->obj, + std::forward(args...)); + }; + + operator FunVPPtr() const + { + if (FP == kind) + { + return vPtrToFunPtrExec; + } + else + { + return vPtrToFunAPtrExec; + } + } + + void* arg() const + { + if (FP == kind) + { + return reinterpret_cast(fn); + } + else + { + return const_cast(this); + } + } + + R IRAM_ATTR operator()(P... args) const + { + if (FP == kind) + { + return fn(std::forward(args...)); + } + else + { + return fnA(obj, std::forward(args...)); + } + } + + protected: + enum { FP, FPA } kind; + union { + FunPtr fn; + FunAPtr fnA; + }; + A obj; + }; +#endif + +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + template + class DelegatePImpl { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; + using FunctionType = std::function; + using FunVPPtr = R(*)(void*, P...); + public: + DelegatePImpl() + { + kind = FP; + fn = nullptr; + } + + DelegatePImpl(std::nullptr_t) + { + kind = FP; + fn = nullptr; + } + + ~DelegatePImpl() + { + if (FUNC == kind) + functional.~FunctionType(); + } + + DelegatePImpl(const DelegatePImpl& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(del.functional); + } + else + { + fn = del.fn; + } + } + + DelegatePImpl(DelegatePImpl&& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(std::move(del.functional)); + } + else + { + fn = del.fn; + } + } + + DelegatePImpl(FunPtr fn) + { + kind = FP; + DelegatePImpl::fn = fn; + } + + template DelegatePImpl(F functional) + { + kind = FUNC; + new (&this->functional) FunctionType(std::forward(functional)); + } + + DelegatePImpl& operator=(const DelegatePImpl& del) + { + if (this == &del) return *this; + if (FUNC == kind && FUNC != del.kind) + { + functional.~FunctionType(); + } + else if (FUNC != kind && FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + kind = del.kind; + if (FUNC == del.kind) + { + functional = del.functional; + } + else + { + fn = del.fn; + } + return *this; + } + + DelegatePImpl& operator=(DelegatePImpl&& del) + { + if (this == &del) return *this; + if (FUNC == kind && FUNC != del.kind) + { + functional.~FunctionType(); + } + else if (FUNC != kind && FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + kind = del.kind; + if (FUNC == del.kind) + { + functional = std::move(del.functional); + } + else + { + fn = del.fn; + } + return *this; + } + + DelegatePImpl& operator=(FunPtr fn) + { + if (FUNC == kind) + { + functional.~FunctionType(); + kind = FP; + } + DelegatePImpl::fn = fn; + return *this; + } + + DelegatePImpl& IRAM_ATTR operator=(std::nullptr_t) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + kind = FP; + fn = nullptr; + return *this; + } + + operator bool() const + { + if (FP == kind) + { + return fn; + } + else + { + return functional ? true : false; + } + } + + operator FunVPPtr() const + { + if (FP == kind) + { + return vPtrToFunPtrExec; + } + else + { + return [](void* self, P... args) -> R + { + return static_cast(self)->functional(std::forward(args...)); + }; + } + } + + void* arg() const + { + if (FP == kind) + { + return reinterpret_cast(fn); + } + else + { + return const_cast(this); + } + } + + operator FunctionType() const + { + if (FP == kind) + { + return fn; + } + else + { + return functional; + } + } + + R IRAM_ATTR operator()(P... args) const + { + if (FP == kind) + { + return fn(std::forward(args...)); + } + else + { + return functional(std::forward(args...)); + } + } + + protected: + enum { FUNC, FP } kind; + union { + FunctionType functional; + FunPtr fn; + }; + }; +#else + template + class DelegatePImpl { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; + using FunVPPtr = R(*)(void*, P...); + public: + DelegatePImpl() + { + fn = nullptr; + } + + DelegatePImpl(std::nullptr_t) + { + fn = nullptr; + } + + DelegatePImpl(const DelegatePImpl& del) + { + fn = del.fn; + } + + DelegatePImpl(DelegatePImpl&& del) + { + fn = std::move(del.fn); + } + + DelegatePImpl(FunPtr fn) + { + DelegatePImpl::fn = fn; + } + + template DelegatePImpl(F fn) + { + DelegatePImpl::fn = std::forward(fn); + } + + DelegatePImpl& operator=(const DelegatePImpl& del) + { + if (this == &del) return *this; + fn = del.fn; + return *this; + } + + DelegatePImpl& operator=(DelegatePImpl&& del) + { + if (this == &del) return *this; + fn = std::move(del.fn); + return *this; + } + + DelegatePImpl& operator=(FunPtr fn) + { + DelegatePImpl::fn = fn; + return *this; + } + + DelegatePImpl& IRAM_ATTR operator=(std::nullptr_t) + { + fn = nullptr; + return *this; + } + + operator bool() const + { + return fn; + } + + operator FunVPPtr() const + { + return vPtrToFunPtrExec; + } + + void* arg() const + { + return reinterpret_cast(fn); + } + + R IRAM_ATTR operator()(P... args) const + { + return fn(std::forward(args...)); + } + + protected: + FunPtr fn; + }; +#endif + +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + template + class DelegateImpl { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A); + using FunctionType = std::function; + using FunVPPtr = R(*)(void*); + public: + DelegateImpl() + { + kind = FP; + fn = nullptr; + } + + DelegateImpl(std::nullptr_t) + { + kind = FP; + fn = nullptr; + } + + ~DelegateImpl() + { + if (FUNC == kind) + functional.~FunctionType(); + else if (FPA == kind) + obj.~A(); + } + + DelegateImpl(const DelegateImpl& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(del.functional); + } + else if (FPA == del.kind) + { + fnA = del.fnA; + new (&obj) A(del.obj); + } + else + { + fn = del.fn; + } + } + + DelegateImpl(DelegateImpl&& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(std::move(del.functional)); + } + else if (FPA == del.kind) + { + fnA = del.fnA; + new (&obj) A(std::move(del.obj)); + } + else + { + fn = del.fn; + } + } + + DelegateImpl(FunAPtr fnA, const A& obj) + { + kind = FPA; + DelegateImpl::fnA = fnA; + new (&this->obj) A(obj); + } + + DelegateImpl(FunAPtr fnA, A&& obj) + { + kind = FPA; + DelegateImpl::fnA = fnA; + new (&this->obj) A(std::move(obj)); + } + + DelegateImpl(FunPtr fn) + { + kind = FP; + DelegateImpl::fn = fn; + } + + template DelegateImpl(F functional) + { + kind = FUNC; + new (&this->functional) FunctionType(std::forward(functional)); + } + + DelegateImpl& operator=(const DelegateImpl& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + if (FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + else if (FPA == del.kind) + { + new (&obj) A; + } + kind = del.kind; + } + if (FUNC == del.kind) + { + functional = del.functional; + } + else if (FPA == del.kind) + { + fnA = del.fnA; + obj = del.obj; + } + else + { + fn = del.fn; + } + return *this; + } + + DelegateImpl& operator=(DelegateImpl&& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + if (FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + else if (FPA == del.kind) + { + new (&obj) A; + } + kind = del.kind; + } + if (FUNC == del.kind) + { + functional = std::move(del.functional); + } + else if (FPA == del.kind) + { + fnA = del.fnA; + obj = std::move(del.obj); + } + else + { + fn = del.fn; + } + return *this; + } + + DelegateImpl& operator=(FunPtr fn) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + kind = FP; + this->fn = fn; + return *this; + } + + DelegateImpl& IRAM_ATTR operator=(std::nullptr_t) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + else if (FPA == kind) + { + obj.~A(); + } + kind = FP; + fn = nullptr; + return *this; + } + + operator bool() const + { + if (FP == kind) + { + return fn; + } + else if (FPA == kind) + { + return fnA; + } + else + { + return functional ? true : false; + } + } + + static R IRAM_ATTR vPtrToFunAPtrExec(void* self) + { + return static_cast(self)->fnA( + static_cast(self)->obj); + }; + + operator FunVPPtr() const + { + if (FP == kind) + { + return reinterpret_cast(fn); + } + else if (FPA == kind) + { + return vPtrToFunAPtrExec; + } + else + { + return [](void* self) -> R + { + return static_cast(self)->functional(); + }; + } + } + + void* arg() const + { + if (FP == kind) + { + return nullptr; + } + else + { + return const_cast(this); + } + } + + operator FunctionType() const + { + if (FP == kind) + { + return fn; + } + else if (FPA == kind) + { + return [this]() { return fnA(obj); }; + } + else + { + return functional; + } + } + + R IRAM_ATTR operator()() const + { + if (FP == kind) + { + return fn(); + } + else if (FPA == kind) + { + return fnA(obj); + } + else + { + return functional(); + } + } + + protected: + enum { FUNC, FP, FPA } kind; + union { + FunctionType functional; + FunPtr fn; + struct { + FunAPtr fnA; + A obj; + }; + }; + }; +#else + template + class DelegateImpl { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A); + using FunVPPtr = R(*)(void*); + public: + DelegateImpl() + { + kind = FP; + fn = nullptr; + } + + DelegateImpl(std::nullptr_t) + { + kind = FP; + fn = nullptr; + } + + DelegateImpl(const DelegateImpl& del) + { + kind = del.kind; + if (FPA == del.kind) + { + fnA = del.fnA; + obj = del.obj; + } + else + { + fn = del.fn; + } + } + + DelegateImpl(DelegateImpl&& del) + { + kind = del.kind; + if (FPA == del.kind) + { + fnA = del.fnA; + obj = std::move(del.obj); + } + else + { + fn = del.fn; + } + } + + DelegateImpl(FunAPtr fnA, const A& obj) + { + kind = FPA; + DelegateImpl::fnA = fnA; + this->obj = obj; + } + + DelegateImpl(FunAPtr fnA, A&& obj) + { + kind = FPA; + DelegateImpl::fnA = fnA; + this->obj = std::move(obj); + } + + DelegateImpl(FunPtr fn) + { + kind = FP; + DelegateImpl::fn = fn; + } + + template DelegateImpl(F fn) + { + kind = FP; + DelegateImpl::fn = std::forward(fn); + } + + DelegateImpl& operator=(const DelegateImpl& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FPA == kind) + { + obj = {}; + } + kind = del.kind; + } + if (FPA == del.kind) + { + fnA = del.fnA; + obj = del.obj; + } + else + { + fn = del.fn; + } + return *this; + } + + DelegateImpl& operator=(DelegateImpl&& del) + { + if (this == &del) return *this; + if (kind != del.kind) + { + if (FPA == kind) + { + obj = {}; + } + kind = del.kind; + } + if (FPA == del.kind) + { + fnA = del.fnA; + obj = std::move(del.obj); + } + else + { + fn = del.fn; + } + return *this; + } + + DelegateImpl& operator=(FunPtr fn) + { + if (FPA == kind) + { + obj = {}; + } + kind = FP; + this->fn = fn; + return *this; + } + + DelegateImpl& IRAM_ATTR operator=(std::nullptr_t) + { + if (FPA == kind) + { + obj = {}; + } + kind = FP; + fn = nullptr; + return *this; + } + + operator bool() const + { + if (FP == kind) + { + return fn; + } + else + { + return fnA; + } + } + + static R IRAM_ATTR vPtrToFunAPtrExec(void* self) + { + return static_cast(self)->fnA( + static_cast(self)->obj); + }; + + operator FunVPPtr() const + { + if (FP == kind) + { + return reinterpret_cast(fn); + } + else + { + return vPtrToFunAPtrExec; + } + } + + void* arg() const + { + if (FP == kind) + { + return nullptr; + } + else + { + return const_cast(this); + } + } + + R IRAM_ATTR operator()() const + { + if (FP == kind) + { + return fn(); + } + else + { + return fnA(obj); + } + } + + protected: + enum { FP, FPA } kind; + union { + FunPtr fn; + FunAPtr fnA; + }; + A obj; + }; +#endif + +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + template + class DelegateImpl { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; + using FunctionType = std::function; + using FunVPPtr = R(*)(void*); + public: + DelegateImpl() + { + kind = FP; + fn = nullptr; + } + + DelegateImpl(std::nullptr_t) + { + kind = FP; + fn = nullptr; + } + + ~DelegateImpl() + { + if (FUNC == kind) + functional.~FunctionType(); + } + + DelegateImpl(const DelegateImpl& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(del.functional); + } + else + { + fn = del.fn; + } + } + + DelegateImpl(DelegateImpl&& del) + { + kind = del.kind; + if (FUNC == del.kind) + { + new (&functional) FunctionType(std::move(del.functional)); + } + else + { + fn = del.fn; + } + } + + DelegateImpl(FunPtr fn) + { + kind = FP; + DelegateImpl::fn = fn; + } + + template DelegateImpl(F functional) + { + kind = FUNC; + new (&this->functional) FunctionType(std::forward(functional)); + } + + DelegateImpl& operator=(const DelegateImpl& del) + { + if (this == &del) return *this; + if (FUNC == kind && FUNC != del.kind) + { + functional.~FunctionType(); + } + else if (FUNC != kind && FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + kind = del.kind; + if (FUNC == del.kind) + { + functional = del.functional; + } + else + { + fn = del.fn; + } + return *this; + } + + DelegateImpl& operator=(DelegateImpl&& del) + { + if (this == &del) return *this; + if (FUNC == kind && FUNC != del.kind) + { + functional.~FunctionType(); + } + else if (FUNC != kind && FUNC == del.kind) + { + new (&this->functional) FunctionType(); + } + kind = del.kind; + if (FUNC == del.kind) + { + functional = std::move(del.functional); + } + else + { + fn = del.fn; + } + return *this; + } + + DelegateImpl& operator=(FunPtr fn) + { + if (FUNC == kind) + { + functional.~FunctionType(); + kind = FP; + } + DelegateImpl::fn = fn; + return *this; + } + + DelegateImpl& IRAM_ATTR operator=(std::nullptr_t) + { + if (FUNC == kind) + { + functional.~FunctionType(); + } + kind = FP; + fn = nullptr; + return *this; + } + + operator bool() const + { + if (FP == kind) + { + return fn; + } + else + { + return functional ? true : false; + } + } + + operator FunVPPtr() const + { + if (FP == kind) + { + return reinterpret_cast(fn); + } + else + { + return [](void* self) -> R + { + return static_cast(self)->functional(); + }; + } + } + + void* arg() const + { + if (FP == kind) + { + return nullptr; + } + else + { + return const_cast(this); + } + } + + operator FunctionType() const + { + if (FP == kind) + { + return fn; + } + else + { + return functional; + } + } + + R IRAM_ATTR operator()() const + { + if (FP == kind) + { + return fn(); + } + else + { + return functional(); + } + } + + protected: + enum { FUNC, FP } kind; + union { + FunctionType functional; + FunPtr fn; + }; + }; +#else + template + class DelegateImpl { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; + using FunVPPtr = R(*)(void*); + public: + DelegateImpl() + { + fn = nullptr; + } + + DelegateImpl(std::nullptr_t) + { + fn = nullptr; + } + + DelegateImpl(const DelegateImpl& del) + { + fn = del.fn; + } + + DelegateImpl(DelegateImpl&& del) + { + fn = std::move(del.fn); + } + + DelegateImpl(FunPtr fn) + { + DelegateImpl::fn = fn; + } + + template DelegateImpl(F fn) + { + DelegateImpl::fn = std::forward(fn); + } + + DelegateImpl& operator=(const DelegateImpl& del) + { + if (this == &del) return *this; + fn = del.fn; + return *this; + } + + DelegateImpl& operator=(DelegateImpl&& del) + { + if (this == &del) return *this; + fn = std::move(del.fn); + return *this; + } + + DelegateImpl& operator=(FunPtr fn) + { + DelegateImpl::fn = fn; + return *this; + } + + DelegateImpl& IRAM_ATTR operator=(std::nullptr_t) + { + fn = nullptr; + return *this; + } + + operator bool() const + { + return fn; + } + + operator FunVPPtr() const + { + return reinterpret_cast(fn); + } + + void* arg() const + { + return nullptr; + } + + R IRAM_ATTR operator()() const + { + return fn(); + } + + protected: + FunPtr fn; + }; +#endif + + template + class Delegate : private detail::DelegatePImpl + { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A, P...); + using FunVPPtr = R(*)(void*, P...); +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + using FunctionType = std::function; +#endif + public: + using detail::DelegatePImpl::operator bool; + using detail::DelegatePImpl::arg; + using detail::DelegatePImpl::operator(); + + operator FunVPPtr() { return detail::DelegatePImpl::operator FunVPPtr(); } +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + operator FunctionType() { return detail::DelegatePImpl::operator FunctionType(); } +#endif + + Delegate() : detail::DelegatePImpl::DelegatePImpl() {} + + Delegate(std::nullptr_t) : detail::DelegatePImpl::DelegatePImpl(nullptr) {} + + Delegate(const Delegate& del) : detail::DelegatePImpl::DelegatePImpl( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : detail::DelegatePImpl::DelegatePImpl( + std::move(static_cast&>(del))) {} + + Delegate(FunAPtr fnA, const A& obj) : detail::DelegatePImpl::DelegatePImpl(fnA, obj) {} + + Delegate(FunAPtr fnA, A&& obj) : detail::DelegatePImpl::DelegatePImpl(fnA, std::move(obj)) {} + + Delegate(FunPtr fn) : detail::DelegatePImpl::DelegatePImpl(fn) {} + + template Delegate(F functional) : detail::DelegatePImpl::DelegatePImpl(functional) {} + + Delegate& operator=(const Delegate& del) { + detail::DelegatePImpl::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + detail::DelegatePImpl::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(FunPtr fn) { + detail::DelegatePImpl::operator=(fn); + return *this; + } + + Delegate& IRAM_ATTR operator=(std::nullptr_t) { + detail::DelegatePImpl::operator=(nullptr); + return *this; + } + }; + + template + class Delegate : private detail::DelegatePImpl + { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A*, P...); + using FunVPPtr = R(*)(void*, P...); +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + using FunctionType = std::function; +#endif + public: + using detail::DelegatePImpl::operator bool; + using detail::DelegatePImpl::operator(); + + operator FunVPPtr() const + { + if (detail::DelegatePImpl::FPA == detail::DelegatePImpl::kind) + { + return reinterpret_cast(detail::DelegatePImpl::fnA); + } + else + { + return detail::DelegatePImpl::operator FunVPPtr(); + } + } +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + operator FunctionType() { return detail::DelegatePImpl::operator FunctionType(); } +#endif + void* arg() const + { + if (detail::DelegatePImpl::FPA == detail::DelegatePImpl::kind) + { + return detail::DelegatePImpl::obj; + } + else + { + return detail::DelegatePImpl::arg(); + } + } + + Delegate() : detail::DelegatePImpl::DelegatePImpl() {} + + Delegate(std::nullptr_t) : detail::DelegatePImpl::DelegatePImpl(nullptr) {} + + Delegate(const Delegate& del) : detail::DelegatePImpl::DelegatePImpl( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : detail::DelegatePImpl::DelegatePImpl( + std::move(static_cast&>(del))) {} + + Delegate(FunAPtr fnA, A* obj) : detail::DelegatePImpl::DelegatePImpl(fnA, obj) {} + + Delegate(FunPtr fn) : detail::DelegatePImpl::DelegatePImpl(fn) {} + + template Delegate(F functional) : detail::DelegatePImpl::DelegatePImpl(functional) {} + + Delegate& operator=(const Delegate& del) { + detail::DelegatePImpl::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + detail::DelegatePImpl::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(FunPtr fn) { + detail::DelegatePImpl::operator=(fn); + return *this; + } + + Delegate& IRAM_ATTR operator=(std::nullptr_t) { + detail::DelegatePImpl::operator=(nullptr); + return *this; + } + }; + + template + class Delegate : private detail::DelegatePImpl + { + public: + using target_type = R(P...); + protected: + using FunPtr = target_type*; +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + using FunctionType = std::function; +#endif + using FunVPPtr = R(*)(void*, P...); + public: + using detail::DelegatePImpl::operator bool; + using detail::DelegatePImpl::arg; + using detail::DelegatePImpl::operator(); + + operator FunVPPtr() const { return detail::DelegatePImpl::operator FunVPPtr(); } +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + operator FunctionType() { return detail::DelegatePImpl::operator FunctionType(); } +#endif + + Delegate() : detail::DelegatePImpl::DelegatePImpl() {} + + Delegate(std::nullptr_t) : detail::DelegatePImpl::DelegatePImpl(nullptr) {} + + Delegate(const Delegate& del) : detail::DelegatePImpl::DelegatePImpl( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : detail::DelegatePImpl::DelegatePImpl( + std::move(static_cast&>(del))) {} + + Delegate(FunPtr fn) : detail::DelegatePImpl::DelegatePImpl(fn) {} + + template Delegate(F functional) : detail::DelegatePImpl::DelegatePImpl(functional) {} + + Delegate& operator=(const Delegate& del) { + detail::DelegatePImpl::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + detail::DelegatePImpl::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(FunPtr fn) { + detail::DelegatePImpl::operator=(fn); + return *this; + } + + Delegate& IRAM_ATTR operator=(std::nullptr_t) { + detail::DelegatePImpl::operator=(nullptr); + return *this; + } + }; + + template + class Delegate : private detail::DelegateImpl + { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A); + using FunVPPtr = R(*)(void*); +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + using FunctionType = std::function; +#endif + public: + using detail::DelegateImpl::operator bool; + using detail::DelegateImpl::arg; + using detail::DelegateImpl::operator(); + + operator FunVPPtr() { return detail::DelegateImpl::operator FunVPPtr(); } +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + operator FunctionType() { return detail::DelegateImpl::operator FunctionType(); } +#endif + + Delegate() : detail::DelegateImpl::DelegateImpl() {} + + Delegate(std::nullptr_t) : detail::DelegateImpl::DelegateImpl(nullptr) {} + + Delegate(const Delegate& del) : detail::DelegateImpl::DelegateImpl( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : detail::DelegateImpl::DelegateImpl( + std::move(static_cast&>(del))) {} + + Delegate(FunAPtr fnA, const A& obj) : detail::DelegateImpl::DelegateImpl(fnA, obj) {} + + Delegate(FunAPtr fnA, A&& obj) : detail::DelegateImpl::DelegateImpl(fnA, std::move(obj)) {} + + Delegate(FunPtr fn) : detail::DelegateImpl::DelegateImpl(fn) {} + + template Delegate(F functional) : detail::DelegateImpl::DelegateImpl(functional) {} + + Delegate& operator=(const Delegate& del) { + detail::DelegateImpl::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + detail::DelegateImpl::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(FunPtr fn) { + detail::DelegateImpl::operator=(fn); + return *this; + } + + Delegate& IRAM_ATTR operator=(std::nullptr_t) { + detail::DelegateImpl::operator=(nullptr); + return *this; + } + }; + + template + class Delegate : private detail::DelegateImpl + { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; + using FunAPtr = R(*)(A*); + using FunVPPtr = R(*)(void*); +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + using FunctionType = std::function; +#endif + public: + using detail::DelegateImpl::operator bool; + using detail::DelegateImpl::operator(); + + operator FunVPPtr() const + { + if (detail::DelegateImpl::FPA == detail::DelegateImpl::kind) + { + return reinterpret_cast(detail::DelegateImpl::fnA); + } + else + { + return detail::DelegateImpl::operator FunVPPtr(); + } + } +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + operator FunctionType() { return detail::DelegateImpl::operator FunctionType(); } +#endif + void* arg() const + { + if (detail::DelegateImpl::FPA == detail::DelegateImpl::kind) + { + return detail::DelegateImpl::obj; + } + else + { + return detail::DelegateImpl::arg(); + } + } + + Delegate() : detail::DelegateImpl::DelegateImpl() {} + + Delegate(std::nullptr_t) : detail::DelegateImpl::DelegateImpl(nullptr) {} + + Delegate(const Delegate& del) : detail::DelegateImpl::DelegateImpl( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : detail::DelegateImpl::DelegateImpl( + std::move(static_cast&>(del))) {} + + Delegate(FunAPtr fnA, A* obj) : detail::DelegateImpl::DelegateImpl(fnA, obj) {} + + Delegate(FunPtr fn) : detail::DelegateImpl::DelegateImpl(fn) {} + + template Delegate(F functional) : detail::DelegateImpl::DelegateImpl(functional) {} + + Delegate& operator=(const Delegate& del) { + detail::DelegateImpl::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + detail::DelegateImpl::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(FunPtr fn) { + detail::DelegateImpl::operator=(fn); + return *this; + } + + Delegate& IRAM_ATTR operator=(std::nullptr_t) { + detail::DelegateImpl::operator=(nullptr); + return *this; + } + }; + + template + class Delegate : private detail::DelegateImpl + { + public: + using target_type = R(); + protected: + using FunPtr = target_type*; +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + using FunctionType = std::function; +#endif + using FunVPPtr = R(*)(void*); + public: + using detail::DelegateImpl::operator bool; + using detail::DelegateImpl::arg; + using detail::DelegateImpl::operator(); + + operator FunVPPtr() const { return detail::DelegateImpl::operator FunVPPtr(); } +#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) + operator FunctionType() { return detail::DelegateImpl::operator FunctionType(); } +#endif + + Delegate() : detail::DelegateImpl::DelegateImpl() {} + + Delegate(std::nullptr_t) : detail::DelegateImpl::DelegateImpl(nullptr) {} + + Delegate(const Delegate& del) : detail::DelegateImpl::DelegateImpl( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : detail::DelegateImpl::DelegateImpl( + std::move(static_cast&>(del))) {} + + Delegate(FunPtr fn) : detail::DelegateImpl::DelegateImpl(fn) {} + + template Delegate(F functional) : detail::DelegateImpl::DelegateImpl(functional) {} + + Delegate& operator=(const Delegate& del) { + detail::DelegateImpl::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + detail::DelegateImpl::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(FunPtr fn) { + detail::DelegateImpl::operator=(fn); + return *this; + } + + Delegate& IRAM_ATTR operator=(std::nullptr_t) { + detail::DelegateImpl::operator=(nullptr); + return *this; + } + }; + } +} + +template class Delegate; +template class Delegate : public delegate::detail::Delegate +{ +public: + Delegate() : delegate::detail::Delegate::Delegate() {} + + Delegate(std::nullptr_t) : delegate::detail::Delegate::Delegate(nullptr) {} + + Delegate(const Delegate& del) : delegate::detail::Delegate::Delegate( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : delegate::detail::Delegate::Delegate( + std::move(static_cast&>(del))) {} + + Delegate(typename delegate::detail::Delegate::FunAPtr fnA, const A& obj) : delegate::detail::Delegate::Delegate(fnA, obj) {} + + Delegate(typename delegate::detail::Delegate::FunAPtr fnA, A&& obj) : delegate::detail::Delegate::Delegate(fnA, std::move(obj)) {} + + Delegate(typename delegate::detail::Delegate::FunPtr fn) : delegate::detail::Delegate::Delegate(fn) {} + + template Delegate(F functional) : delegate::detail::Delegate::Delegate(functional) {} + + Delegate& operator=(const Delegate& del) { + delegate::detail::Delegate::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + delegate::detail::Delegate::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(typename delegate::detail::Delegate::FunPtr fn) { + delegate::detail::Delegate::operator=(fn); + return *this; + } + + Delegate& IRAM_ATTR operator=(std::nullptr_t) { + delegate::detail::Delegate::operator=(nullptr); + return *this; + } +}; +template class Delegate : public delegate::detail::Delegate +{ +public: + Delegate() : delegate::detail::Delegate::Delegate() {} + + Delegate(std::nullptr_t) : delegate::detail::Delegate::Delegate(nullptr) {} + + Delegate(const Delegate& del) : delegate::detail::Delegate::Delegate( + static_cast&>(del)) {} + + Delegate(Delegate&& del) : delegate::detail::Delegate::Delegate( + std::move(static_cast&>(del))) {} + + Delegate(typename delegate::detail::Delegate::FunPtr fn) : delegate::detail::Delegate::Delegate(fn) {} + + template Delegate(F functional) : delegate::detail::Delegate::Delegate(functional) {} + + Delegate& operator=(const Delegate& del) { + delegate::detail::Delegate::operator=(del); + return *this; + } + + Delegate& operator=(Delegate&& del) { + delegate::detail::Delegate::operator=(std::move(del)); + return *this; + } + + Delegate& operator=(typename delegate::detail::Delegate::FunPtr fn) { + delegate::detail::Delegate::operator=(fn); + return *this; + } + + Delegate& IRAM_ATTR operator=(std::nullptr_t) { + delegate::detail::Delegate::operator=(nullptr); + return *this; + } +}; + +#endif // __Delegate_h diff --git a/circular_queue/MultiDelegate.h b/circular_queue/MultiDelegate.h new file mode 100644 index 0000000..36cbd94 --- /dev/null +++ b/circular_queue/MultiDelegate.h @@ -0,0 +1,567 @@ +/* +MultiDelegate.h - A queue or event multiplexer based on the efficient Delegate +class +Copyright (c) 2019-2020 Dirk O. Kaar. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __MULTIDELEGATE_H +#define __MULTIDELEGATE_H + +#include +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) +#include +#else +#include "circular_queue/ghostl.h" +#endif + +#if defined(ESP8266) +#include +using esp8266::InterruptLock; +#elif defined(ARDUINO) +class InterruptLock { +public: + InterruptLock() { + noInterrupts(); + } + ~InterruptLock() { + interrupts(); + } +}; +#else +#include +#endif + +namespace +{ + + template< typename Delegate, typename R, bool ISQUEUE = false, typename... P> + struct CallP + { + static R execute(Delegate& del, P... args) + { + return del(std::forward(args...)); + } + }; + + template< typename Delegate, bool ISQUEUE, typename... P> + struct CallP + { + static bool execute(Delegate& del, P... args) + { + del(std::forward(args...)); + return true; + } + }; + + template< typename Delegate, typename R, bool ISQUEUE = false> + struct Call + { + static R execute(Delegate& del) + { + return del(); + } + }; + + template< typename Delegate, bool ISQUEUE> + struct Call + { + static bool execute(Delegate& del) + { + del(); + return true; + } + }; + +} + +namespace delegate +{ + namespace detail + { + + template< typename Delegate, typename R, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32, typename... P> + class MultiDelegatePImpl + { + public: + MultiDelegatePImpl() = default; + ~MultiDelegatePImpl() + { + *this = nullptr; + } + + MultiDelegatePImpl(const MultiDelegatePImpl&) = delete; + MultiDelegatePImpl& operator=(const MultiDelegatePImpl&) = delete; + + MultiDelegatePImpl(MultiDelegatePImpl&& md) + { + first = md.first; + last = md.last; + unused = md.unused; + nodeCount = md.nodeCount; + md.first = nullptr; + md.last = nullptr; + md.unused = nullptr; + md.nodeCount = 0; + } + + MultiDelegatePImpl(const Delegate& del) + { + add(del); + } + + MultiDelegatePImpl(Delegate&& del) + { + add(std::move(del)); + } + + MultiDelegatePImpl& operator=(MultiDelegatePImpl&& md) + { + first = md.first; + last = md.last; + unused = md.unused; + nodeCount = md.nodeCount; + md.first = nullptr; + md.last = nullptr; + md.unused = nullptr; + md.nodeCount = 0; + return *this; + } + + MultiDelegatePImpl& operator=(std::nullptr_t) + { + if (last) + last->mNext = unused; + if (first) + unused = first; + while (unused) + { + auto to_delete = unused; + unused = unused->mNext; + delete(to_delete); + } + return *this; + } + + MultiDelegatePImpl& operator+=(const Delegate& del) + { + add(del); + return *this; + } + + MultiDelegatePImpl& operator+=(Delegate&& del) + { + add(std::move(del)); + return *this; + } + + protected: + struct Node_t + { + ~Node_t() + { + mDelegate = nullptr; // special overload in Delegate + } + Node_t* mNext = nullptr; + Delegate mDelegate; + }; + + Node_t* first = nullptr; + Node_t* last = nullptr; + Node_t* unused = nullptr; + size_t nodeCount = 0; + + // Returns a pointer to an unused Node_t, + // or if none are available allocates a new one, + // or nullptr if limit is reached + Node_t* IRAM_ATTR get_node_unsafe() + { + Node_t* result = nullptr; + // try to get an item from unused items list + if (unused) + { + result = unused; + unused = unused->mNext; + } + // if no unused items, and count not too high, allocate a new one + else if (nodeCount < QUEUE_CAPACITY) + { +#if defined(ESP8266) || defined(ESP32) + result = new (std::nothrow) Node_t; +#else + result = new Node_t; +#endif + if (result) + ++nodeCount; + } + return result; + } + + void recycle_node_unsafe(Node_t* node) + { + node->mDelegate = nullptr; // special overload in Delegate + node->mNext = unused; + unused = node; + } + +#ifndef ARDUINO + std::mutex mutex_unused; +#endif + public: + class iterator : public std::iterator + { + public: + Node_t* current = nullptr; + Node_t* prev = nullptr; + const Node_t* stop = nullptr; + + iterator(MultiDelegatePImpl& md) : current(md.first), stop(md.last) {} + iterator() = default; + iterator(const iterator&) = default; + iterator& operator=(const iterator&) = default; + iterator& operator=(iterator&&) = default; + operator bool() const + { + return current && stop; + } + bool operator==(const iterator& rhs) const + { + return current == rhs.current; + } + bool operator!=(const iterator& rhs) const + { + return !operator==(rhs); + } + Delegate& operator*() const + { + return current->mDelegate; + } + Delegate* operator->() const + { + return ¤t->mDelegate; + } + iterator& operator++() // prefix + { + if (current && stop != current) + { + prev = current; + current = current->mNext; + } + else + current = nullptr; // end + return *this; + } + iterator& operator++(int) // postfix + { + iterator tmp(*this); + operator++(); + return tmp; + } + }; + + iterator begin() + { + return iterator(*this); + } + iterator end() const + { + return iterator(); + } + + const Delegate* IRAM_ATTR add(const Delegate& del) + { + return add(Delegate(del)); + } + + const Delegate* IRAM_ATTR add(Delegate&& del) + { + if (!del) + return nullptr; + +#ifdef ARDUINO + InterruptLock lockAllInterruptsInThisScope; +#else + std::lock_guard lock(mutex_unused); +#endif + + Node_t* item = ISQUEUE ? get_node_unsafe() : +#if defined(ESP8266) || defined(ESP32) + new (std::nothrow) Node_t; +#else + new Node_t; +#endif + if (!item) + return nullptr; + + item->mDelegate = std::move(del); + item->mNext = nullptr; + + if (last) + last->mNext = item; + else + first = item; + last = item; + + return &item->mDelegate; + } + + iterator erase(iterator it) + { + if (!it) + return end(); +#ifdef ARDUINO + InterruptLock lockAllInterruptsInThisScope; +#else + std::lock_guard lock(mutex_unused); +#endif + auto to_recycle = it.current; + + if (last == it.current) + last = it.prev; + it.current = it.current->mNext; + if (it.prev) + { + it.prev->mNext = it.current; + } + else + { + first = it.current; + } + if (ISQUEUE) + recycle_node_unsafe(to_recycle); + else + delete to_recycle; + return it; + } + + bool erase(const Delegate* const del) + { + auto it = begin(); + while (it) + { + if (del == &(*it)) + { + erase(it); + return true; + } + ++it; + } + return false; + } + + operator bool() const + { + return first; + } + + R operator()(P... args) + { + auto it = begin(); + if (!it) + return {}; + + static std::atomic fence(false); + // prevent recursive calls +#if defined(ARDUINO) && !defined(ESP32) + if (fence.load()) return {}; + fence.store(true); +#else + if (fence.exchange(true)) return {}; +#endif + + R result; + do + { + result = CallP::execute(*it, args...); + if (result && ISQUEUE) + it = erase(it); + else + ++it; +#if defined(ESP8266) || defined(ESP32) + // running callbacks might last too long for watchdog etc. + optimistic_yield(10000); +#endif + } while (it); + + fence.store(false); + return result; + } + }; + + template< typename Delegate, typename R = void, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32> + class MultiDelegateImpl : public MultiDelegatePImpl + { + public: + using MultiDelegatePImpl::MultiDelegatePImpl; + + R operator()() + { + auto it = this->begin(); + if (!it) + return {}; + + static std::atomic fence(false); + // prevent recursive calls +#if defined(ARDUINO) && !defined(ESP32) + if (fence.load()) return {}; + fence.store(true); +#else + if (fence.exchange(true)) return {}; +#endif + + R result; + do + { + result = Call::execute(*it); + if (result && ISQUEUE) + it = this->erase(it); + else + ++it; +#if defined(ESP8266) || defined(ESP32) + // running callbacks might last too long for watchdog etc. + optimistic_yield(10000); +#endif + } while (it); + + fence.store(false); + return result; + } + }; + + template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P> class MultiDelegate; + + template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P> + class MultiDelegate : public MultiDelegatePImpl + { + public: + using MultiDelegatePImpl::MultiDelegatePImpl; + }; + + template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY> + class MultiDelegate : public MultiDelegateImpl + { + public: + using MultiDelegateImpl::MultiDelegateImpl; + }; + + template< typename Delegate, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P> + class MultiDelegate : public MultiDelegatePImpl + { + public: + using MultiDelegatePImpl::MultiDelegatePImpl; + + void operator()(P... args) + { + auto it = this->begin(); + if (!it) + return; + + static std::atomic fence(false); + // prevent recursive calls +#if defined(ARDUINO) && !defined(ESP32) + if (fence.load()) return; + fence.store(true); +#else + if (fence.exchange(true)) return; +#endif + + do + { + CallP::execute(*it, args...); + if (ISQUEUE) + it = this->erase(it); + else + ++it; +#if defined(ESP8266) || defined(ESP32) + // running callbacks might last too long for watchdog etc. + optimistic_yield(10000); +#endif + } while (it); + + fence.store(false); + } + }; + + template< typename Delegate, bool ISQUEUE, size_t QUEUE_CAPACITY> + class MultiDelegate : public MultiDelegateImpl + { + public: + using MultiDelegateImpl::MultiDelegateImpl; + + void operator()() + { + auto it = this->begin(); + if (!it) + return; + + static std::atomic fence(false); + // prevent recursive calls +#if defined(ARDUINO) && !defined(ESP32) + if (fence.load()) return; + fence.store(true); +#else + if (fence.exchange(true)) return; +#endif + + do + { + Call::execute(*it); + if (ISQUEUE) + it = this->erase(it); + else + ++it; +#if defined(ESP8266) || defined(ESP32) + // running callbacks might last too long for watchdog etc. + optimistic_yield(10000); +#endif + } while (it); + + fence.store(false); + } + }; + + } + +} + +/** +The MultiDelegate class template can be specialized to either a queue or an event multiplexer. +It is designed to be used with Delegate, the efficient runtime wrapper for C function ptr and C++ std::function. +@tparam Delegate specifies the concrete type that MultiDelegate bases the queue or event multiplexer on. +@tparam ISQUEUE modifies the generated MultiDelegate class in subtle ways. In queue mode (ISQUEUE == true), + the value of QUEUE_CAPACITY enforces the maximum number of simultaneous items the queue can contain. + This is exploited to minimize the use of new and delete by reusing already allocated items, thus + reducing heap fragmentation. In event multiplexer mode (ISQUEUE = false), new and delete are + used for allocation of the event handler items. + If the result type of the function call operator of Delegate is void, calling a MultiDelegate queue + removes each item after calling it; a Multidelegate event multiplexer keeps event handlers until + explicitly removed. + If the result type of the function call operator of Delegate is non-void, in a MultiDelegate queue + the type-conversion to bool of that result determines if the item is immediately removed or kept + after each call: if true is returned, the item is removed. A Multidelegate event multiplexer keeps event + handlers until they are explicitly removed. +@tparam QUEUE_CAPACITY is only used if ISQUEUE == true. Then, it sets the maximum capacity that the queue dynamically + allocates from the heap. Unused items are not returned to the heap, but are managed by the MultiDelegate + instance during its own lifetime for efficiency. +*/ +template< typename Delegate, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32> +class MultiDelegate : public delegate::detail::MultiDelegate +{ +public: + using delegate::detail::MultiDelegate::MultiDelegate; +}; + +#endif // __MULTIDELEGATE_H diff --git a/circular_queue/circular_queue.h b/circular_queue/circular_queue.h new file mode 100644 index 0000000..58127a7 --- /dev/null +++ b/circular_queue/circular_queue.h @@ -0,0 +1,399 @@ +/* +circular_queue.h - Implementation of a lock-free circular queue for EspSoftwareSerial. +Copyright (c) 2019 Dirk O. Kaar. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __circular_queue_h +#define __circular_queue_h + +#ifdef ARDUINO +#include +#endif + +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) +#include +#include +#include +#include +using std::min; +#else +#include "ghostl.h" +#endif + +#if !defined(ESP32) && !defined(ESP8266) +#define ICACHE_RAM_ATTR +#define IRAM_ATTR +#endif + +/*! + @brief Instance class for a single-producer, single-consumer circular queue / ring buffer (FIFO). + This implementation is lock-free between producer and consumer for the available(), peek(), + pop(), and push() type functions. +*/ +template< typename T > +class circular_queue +{ +public: + /*! + @brief Constructs a valid, but zero-capacity dummy queue. + */ + circular_queue() : m_bufSize(1) + { + m_inPos.store(0); + m_outPos.store(0); + } + /*! + @brief Constructs a queue of the given maximum capacity. + */ + circular_queue(const size_t capacity) : m_bufSize(capacity + 1), m_buffer(new T[m_bufSize]) + { + m_inPos.store(0); + m_outPos.store(0); + } + circular_queue(circular_queue&& cq) : + m_bufSize(cq.m_bufSize), m_buffer(cq.m_buffer), m_inPos(cq.m_inPos.load()), m_outPos(cq.m_outPos.load()) + {} + ~circular_queue() + { + m_buffer.reset(); + } + circular_queue(const circular_queue&) = delete; + circular_queue& operator=(circular_queue&& cq) + { + m_bufSize = cq.m_bufSize; + m_buffer = cq.m_buffer; + m_inPos.store(cq.m_inPos.load()); + m_outPos.store(cq.m_outPos.load()); + } + circular_queue& operator=(const circular_queue&) = delete; + + /*! + @brief Get the numer of elements the queue can hold at most. + */ + size_t capacity() const + { + return m_bufSize - 1; + } + + /*! + @brief Resize the queue. The available elements in the queue are preserved. + This is not lock-free and concurrent producer or consumer access + will lead to corruption. + @return True if the new capacity could accommodate the present elements in + the queue, otherwise nothing is done and false is returned. + */ + bool capacity(const size_t cap); + + /*! + @brief Discard all data in the queue. + */ + void flush() + { + m_outPos.store(m_inPos.load()); + } + + /*! + @brief Get a snapshot number of elements that can be retrieved by pop. + */ + size_t available() const + { + int avail = static_cast(m_inPos.load() - m_outPos.load()); + if (avail < 0) avail += m_bufSize; + return avail; + } + + /*! + @brief Get the remaining free elementes for pushing. + */ + size_t available_for_push() const + { + int avail = static_cast(m_outPos.load() - m_inPos.load()) - 1; + if (avail < 0) avail += m_bufSize; + return avail; + } + + /*! + @brief Peek at the next element pop will return without removing it from the queue. + @return An rvalue copy of the next element that can be popped. If the queue is empty, + return an rvalue copy of the element that is pending the next push. + */ + T peek() const + { + const auto outPos = m_outPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + return m_buffer[outPos]; + } + + /*! + @brief Peek at the next pending input value. + @return A reference to the next element that can be pushed. + */ + T& IRAM_ATTR pushpeek() + { + const auto inPos = m_inPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + return m_buffer[inPos]; + } + + /*! + @brief Release the next pending input value, accessible by pushpeek(), into the queue. + @return true if the queue accepted the value, false if the queue + was full. + */ + bool IRAM_ATTR push(); + + /*! + @brief Move the rvalue parameter into the queue. + @return true if the queue accepted the value, false if the queue + was full. + */ + bool IRAM_ATTR push(T&& val); + + /*! + @brief Push a copy of the parameter into the queue. + @return true if the queue accepted the value, false if the queue + was full. + */ + bool IRAM_ATTR push(const T& val) + { + return push(T(val)); + } + +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) + /*! + @brief Push copies of multiple elements from a buffer into the queue, + in order, beginning at buffer's head. + @return The number of elements actually copied into the queue, counted + from the buffer head. + */ + size_t push_n(const T* buffer, size_t size); +#endif + + /*! + @brief Pop the next available element from the queue. + @return An rvalue copy of the popped element, or a default + value of type T if the queue is empty. + */ + T pop(); + +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) + /*! + @brief Pop multiple elements in ordered sequence from the queue to a buffer. + If buffer is nullptr, simply discards up to size elements from the queue. + @return The number of elements actually popped from the queue to + buffer. + */ + size_t pop_n(T* buffer, size_t size); +#endif + + /*! + @brief Iterate over and remove each available element from queue, + calling back fun with an rvalue reference of every single element. + */ +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) + void for_each(const std::function& fun); +#else + void for_each(std::function fun); +#endif + + /*! + @brief In reverse order, iterate over, pop and optionally requeue each available element from the queue, + calling back fun with a reference of every single element. + Requeuing is dependent on the return boolean of the callback function. If it + returns true, the requeue occurs. + */ +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) + bool for_each_rev_requeue(const std::function& fun); +#else + bool for_each_rev_requeue(std::function fun); +#endif + +protected: + const T defaultValue = {}; + unsigned m_bufSize; +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) + std::unique_ptr m_buffer; +#else + std::unique_ptr m_buffer; +#endif + std::atomic m_inPos; + std::atomic m_outPos; +}; + +template< typename T > +bool circular_queue::capacity(const size_t cap) +{ + if (cap + 1 == m_bufSize) return true; + else if (available() > cap) return false; + std::unique_ptr buffer(new T[cap + 1]); + const auto available = pop_n(buffer, cap); + m_buffer.reset(buffer); + m_bufSize = cap + 1; + std::atomic_thread_fence(std::memory_order_release); + m_inPos.store(available, std::memory_order_relaxed); + m_outPos.store(0, std::memory_order_release); + return true; +} + +template< typename T > +bool IRAM_ATTR circular_queue::push() +{ + const auto inPos = m_inPos.load(std::memory_order_acquire); + const unsigned next = (inPos + 1) % m_bufSize; + if (next == m_outPos.load(std::memory_order_relaxed)) { + return false; + } + + std::atomic_thread_fence(std::memory_order_acquire); + + m_inPos.store(next, std::memory_order_release); + return true; +} + +template< typename T > +bool IRAM_ATTR circular_queue::push(T&& val) +{ + const auto inPos = m_inPos.load(std::memory_order_acquire); + const unsigned next = (inPos + 1) % m_bufSize; + if (next == m_outPos.load(std::memory_order_relaxed)) { + return false; + } + + std::atomic_thread_fence(std::memory_order_acquire); + + m_buffer[inPos] = std::move(val); + + std::atomic_thread_fence(std::memory_order_release); + + m_inPos.store(next, std::memory_order_release); + return true; +} + +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) +template< typename T > +size_t circular_queue::push_n(const T* buffer, size_t size) +{ + const auto inPos = m_inPos.load(std::memory_order_acquire); + const auto outPos = m_outPos.load(std::memory_order_relaxed); + + size_t blockSize = (outPos > inPos) ? outPos - 1 - inPos : (outPos == 0) ? m_bufSize - 1 - inPos : m_bufSize - inPos; + blockSize = min(size, blockSize); + if (!blockSize) return 0; + int next = (inPos + blockSize) % m_bufSize; + + std::atomic_thread_fence(std::memory_order_acquire); + + auto dest = m_buffer.get() + inPos; + std::copy_n(std::make_move_iterator(buffer), blockSize, dest); + size = min(size - blockSize, outPos > 1 ? static_cast(outPos - next - 1) : 0); + next += size; + dest = m_buffer.get(); + std::copy_n(std::make_move_iterator(buffer + blockSize), size, dest); + + std::atomic_thread_fence(std::memory_order_release); + + m_inPos.store(next, std::memory_order_release); + return blockSize + size; +} +#endif + +template< typename T > +T circular_queue::pop() +{ + const auto outPos = m_outPos.load(std::memory_order_acquire); + if (m_inPos.load(std::memory_order_relaxed) == outPos) return defaultValue; + + std::atomic_thread_fence(std::memory_order_acquire); + + auto val = std::move(m_buffer[outPos]); + + std::atomic_thread_fence(std::memory_order_release); + + m_outPos.store((outPos + 1) % m_bufSize, std::memory_order_release); + return val; +} + +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) +template< typename T > +size_t circular_queue::pop_n(T* buffer, size_t size) { + size_t avail = size = min(size, available()); + if (!avail) return 0; + const auto outPos = m_outPos.load(std::memory_order_acquire); + size_t n = min(avail, static_cast(m_bufSize - outPos)); + + std::atomic_thread_fence(std::memory_order_acquire); + + if (buffer) { + buffer = std::copy_n(std::make_move_iterator(m_buffer.get() + outPos), n, buffer); + avail -= n; + std::copy_n(std::make_move_iterator(m_buffer.get()), avail, buffer); + } + + std::atomic_thread_fence(std::memory_order_release); + + m_outPos.store((outPos + size) % m_bufSize, std::memory_order_release); + return size; +} +#endif + +template< typename T > +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) +void circular_queue::for_each(const std::function& fun) +#else +void circular_queue::for_each(std::function fun) +#endif +{ + auto outPos = m_outPos.load(std::memory_order_acquire); + const auto inPos = m_inPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + while (outPos != inPos) + { + fun(std::move(m_buffer[outPos])); + std::atomic_thread_fence(std::memory_order_release); + outPos = (outPos + 1) % m_bufSize; + m_outPos.store(outPos, std::memory_order_release); + } +} + +template< typename T > +#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) +bool circular_queue::for_each_rev_requeue(const std::function& fun) +#else +bool circular_queue::for_each_rev_requeue(std::function fun) +#endif +{ + auto inPos0 = circular_queue::m_inPos.load(std::memory_order_acquire); + auto outPos = circular_queue::m_outPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + if (outPos == inPos0) return false; + auto pos = inPos0; + auto outPos1 = inPos0; + const auto posDecr = circular_queue::m_bufSize - 1; + do { + pos = (pos + posDecr) % circular_queue::m_bufSize; + T&& val = std::move(circular_queue::m_buffer[pos]); + if (fun(val)) + { + outPos1 = (outPos1 + posDecr) % circular_queue::m_bufSize; + if (outPos1 != pos) circular_queue::m_buffer[outPos1] = std::move(val); + } + } while (pos != outPos); + circular_queue::m_outPos.store(outPos1, std::memory_order_release); + return true; +} + +#endif // __circular_queue_h diff --git a/circular_queue/circular_queue_mp.h b/circular_queue/circular_queue_mp.h new file mode 100644 index 0000000..1f8d301 --- /dev/null +++ b/circular_queue/circular_queue_mp.h @@ -0,0 +1,200 @@ +/* +circular_queue_mp.h - Implementation of a lock-free circular queue for EspSoftwareSerial. +Copyright (c) 2019 Dirk O. Kaar. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __circular_queue_mp_h +#define __circular_queue_mp_h + +#include "circular_queue.h" + +#ifdef ESP8266 +#include "interrupts.h" +#else +#include +#endif + +/*! + @brief Instance class for a multi-producer, single-consumer circular queue / ring buffer (FIFO). + This implementation is lock-free between producers and consumer for the available(), peek(), + pop(), and push() type functions, but is guarded to safely allow only a single producer + at any instant. +*/ +template< typename T > +class circular_queue_mp : protected circular_queue +{ +public: + circular_queue_mp() = default; + circular_queue_mp(const size_t capacity) : circular_queue(capacity) + {} + circular_queue_mp(circular_queue&& cq) : circular_queue(std::move(cq)) + {} + using circular_queue::operator=; + using circular_queue::capacity; + using circular_queue::flush; + using circular_queue::available; + using circular_queue::available_for_push; + using circular_queue::peek; + using circular_queue::pop; + using circular_queue::pop_n; + using circular_queue::for_each; + using circular_queue::for_each_rev_requeue; + + /*! + @brief Resize the queue. The available elements in the queue are preserved. + This is not lock-free, but safe, concurrent producer or consumer access + is guarded. + @return True if the new capacity could accommodate the present elements in + the queue, otherwise nothing is done and false is returned. + */ + bool capacity(const size_t cap) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + return circular_queue::capacity(cap); + } + + bool IRAM_ATTR push() = delete; + + /*! + @brief Move the rvalue parameter into the queue, guarded + for multiple concurrent producers. + @return true if the queue accepted the value, false if the queue + was full. + */ + bool IRAM_ATTR push(T&& val) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + return circular_queue::push(std::move(val)); + } + + /*! + @brief Push a copy of the parameter into the queue, guarded + for multiple concurrent producers. + @return true if the queue accepted the value, false if the queue + was full. + */ + bool IRAM_ATTR push(const T& val) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + return circular_queue::push(val); + } + + /*! + @brief Push copies of multiple elements from a buffer into the queue, + in order, beginning at buffer's head. This is guarded for + multiple producers, push_n() is atomic. + @return The number of elements actually copied into the queue, counted + from the buffer head. + */ + size_t push_n(const T* buffer, size_t size) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + return circular_queue::push_n(buffer, size); + } + + /*! + @brief Pops the next available element from the queue, requeues + it immediately. + @return A reference to the just requeued element, or the default + value of type T if the queue is empty. + */ + T& pop_requeue(); + + /*! + @brief Iterate over, pop and optionally requeue each available element from the queue, + calling back fun with a reference of every single element. + Requeuing is dependent on the return boolean of the callback function. If it + returns true, the requeue occurs. + */ + bool for_each_requeue(const std::function& fun); + +#ifndef ESP8266 +protected: + std::mutex m_pushMtx; +#endif +}; + +template< typename T > +T& circular_queue_mp::pop_requeue() +{ +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + const auto outPos = circular_queue::m_outPos.load(std::memory_order_acquire); + const auto inPos = circular_queue::m_inPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + if (inPos == outPos) return circular_queue::defaultValue; + T& val = circular_queue::m_buffer[inPos] = std::move(circular_queue::m_buffer[outPos]); + const auto bufSize = circular_queue::m_bufSize; + std::atomic_thread_fence(std::memory_order_release); + circular_queue::m_outPos.store((outPos + 1) % bufSize, std::memory_order_relaxed); + circular_queue::m_inPos.store((inPos + 1) % bufSize, std::memory_order_release); + return val; +} + +template< typename T > +bool circular_queue_mp::for_each_requeue(const std::function& fun) +{ + auto inPos0 = circular_queue::m_inPos.load(std::memory_order_acquire); + auto outPos = circular_queue::m_outPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + if (outPos == inPos0) return false; + do { + T&& val = std::move(circular_queue::m_buffer[outPos]); + if (fun(val)) + { +#ifdef ESP8266 + esp8266::InterruptLock lock; +#else + std::lock_guard lock(m_pushMtx); +#endif + std::atomic_thread_fence(std::memory_order_release); + auto inPos = circular_queue::m_inPos.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + circular_queue::m_buffer[inPos] = std::move(val); + std::atomic_thread_fence(std::memory_order_release); + circular_queue::m_inPos.store((inPos + 1) % circular_queue::m_bufSize, std::memory_order_release); + } + else + { + std::atomic_thread_fence(std::memory_order_release); + } + outPos = (outPos + 1) % circular_queue::m_bufSize; + circular_queue::m_outPos.store(outPos, std::memory_order_release); + } while (outPos != inPos0); + return true; +} + +#endif // __circular_queue_mp_h diff --git a/circular_queue/ghostl.h b/circular_queue/ghostl.h new file mode 100644 index 0000000..f361570 --- /dev/null +++ b/circular_queue/ghostl.h @@ -0,0 +1,80 @@ +/* +ghostl.h - Implementation of a bare-bones, mostly no-op, C++ STL shell + that allows building some Arduino ESP8266/ESP32 + libraries on Aruduino AVR. +Copyright (c) 2019 Dirk O. Kaar. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __ghostl_h +#define __ghostl_h + +#if defined(ARDUINO_ARCH_SAMD) +#include +#endif + +namespace std +{ +#if !defined(ARDUINO_ARCH_SAMD) + typedef enum memory_order { + memory_order_relaxed, + memory_order_acquire, + memory_order_release, + memory_order_seq_cst + } memory_order; + template< typename T > class atomic { + private: + T value; + public: + atomic() {} + atomic(T desired) { value = desired; } + void store(T desired, std::memory_order = std::memory_order_seq_cst) volatile noexcept { value = desired; } + T load(std::memory_order = std::memory_order_seq_cst) const volatile noexcept { return value; } + }; + inline void atomic_thread_fence(std::memory_order order) noexcept {} + template< typename T > T&& move(T& t) noexcept { return static_cast(t); } +#endif + + template< typename T, unsigned long N > struct array + { + T _M_elems[N]; + unsigned long size() const { return N; } + T& operator[](unsigned long i) { return _M_elems[i]; } + const T& operator[](unsigned long i) const { return _M_elems[i]; } + }; + + template< typename T > class unique_ptr + { + public: + using pointer = T*; + unique_ptr() noexcept : ptr(nullptr) {} + unique_ptr(pointer p) : ptr(p) {} + pointer operator->() const noexcept { return ptr; } + T& operator[](size_t i) const { return ptr[i]; } + void reset(pointer p = pointer()) noexcept + { + delete ptr; + ptr = p; + } + T& operator*() const { return *ptr; } + private: + pointer ptr; + }; + + template< typename T > using function = T*; +} + +#endif // __ghostl_h diff --git a/temp/SoftwareSerialOld.cpp b/temp/SoftwareSerialOld.cpp new file mode 100644 index 0000000..05b3d31 --- /dev/null +++ b/temp/SoftwareSerialOld.cpp @@ -0,0 +1,502 @@ +/* + +SoftwareSerialOld.cpp - Implementation of the Arduino software serial for ESP8266/ESP32. +Copyright (c) 2015-2016 Peter Lerup. All rights reserved. +Copyright (c) 2018-2019 Dirk O. Kaar. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include + +#include "SoftwareSerialOld.h" + +// signal quality in ALT_DIGITAL_WRITE is better or equal in all +// tests so far (ESP8266 HW UART, SDS011 PM sensor, SoftwareSerialOld back-to-back). +#define ALT_DIGITAL_WRITE 1 + +#if defined(ESP8266) +constexpr size_t MAX_PIN = 15; +#elif defined(ESP32) +constexpr size_t MAX_PIN = 35; +#endif + +// As the Arduino attachInterrupt has no parameter, lists of objects +// and callbacks corresponding to each possible GPIO pins have to be defined +SoftwareSerialOld *ObjList[MAX_PIN + 1]; + +void ICACHE_RAM_ATTR sws_isr_0() { ObjList[0]->rxRead(); }; +#ifdef ESP32 +// Pin 1 can not be used +#else +void ICACHE_RAM_ATTR sws_isr_1() { ObjList[1]->rxRead(); }; +#endif +void ICACHE_RAM_ATTR sws_isr_2() { ObjList[2]->rxRead(); }; +#ifdef ESP32 +// Pin 3 can not be used +#else +void ICACHE_RAM_ATTR sws_isr_3() { ObjList[3]->rxRead(); }; +#endif +void ICACHE_RAM_ATTR sws_isr_4() { ObjList[4]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_5() { ObjList[5]->rxRead(); }; +// Pin 6 to 11 can not be used +void ICACHE_RAM_ATTR sws_isr_12() { ObjList[12]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_13() { ObjList[13]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_14() { ObjList[14]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_15() { ObjList[15]->rxRead(); }; +#ifdef ESP32 +void ICACHE_RAM_ATTR sws_isr_16() { ObjList[16]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_17() { ObjList[17]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_18() { ObjList[18]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_19() { ObjList[19]->rxRead(); }; +// Pin 20 can not be used +void ICACHE_RAM_ATTR sws_isr_21() { ObjList[21]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_22() { ObjList[22]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_23() { ObjList[23]->rxRead(); }; +// Pin 24 can not be used +void ICACHE_RAM_ATTR sws_isr_25() { ObjList[25]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_26() { ObjList[26]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_27() { ObjList[27]->rxRead(); }; +// Pin 28 to 31 can not be used +void ICACHE_RAM_ATTR sws_isr_32() { ObjList[32]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_33() { ObjList[33]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_34() { ObjList[34]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_35() { ObjList[35]->rxRead(); }; +#endif + +static void (*ISRList[MAX_PIN + 1])() = { + sws_isr_0, +#ifdef ESP32 + 0, +#else + sws_isr_1, +#endif + sws_isr_2, +#ifdef ESP32 + 0, +#else + sws_isr_3, +#endif + sws_isr_4, + sws_isr_5, + 0, + 0, + 0, + 0, + 0, + 0, + sws_isr_12, + sws_isr_13, + sws_isr_14, + sws_isr_15, +#ifdef ESP32 + sws_isr_16, + sws_isr_17, + sws_isr_18, + sws_isr_19, + 0, + sws_isr_21, + sws_isr_22, + sws_isr_23, + 0, + sws_isr_25, + sws_isr_26, + sws_isr_27, + 0, + 0, + 0, + 0, + sws_isr_32, + sws_isr_33, + sws_isr_34, + sws_isr_35, +#endif +}; + +SoftwareSerialOld::SoftwareSerialOld( + int receivePin, int transmitPin, bool inverse_logic, int bufSize, int isrBufSize) { + m_isrBuffer = 0; + m_isrOverflow = false; + m_isrLastCycle = 0; + m_oneWire = (receivePin == transmitPin); + m_invert = inverse_logic; + if (isValidGPIOpin(receivePin)) { + m_rxPin = receivePin; + m_bufSize = bufSize; + m_buffer = (uint8_t*)malloc(m_bufSize); + m_isrBufSize = isrBufSize ? isrBufSize : 10 * bufSize; + m_isrBuffer = static_cast*>(malloc(m_isrBufSize * sizeof(uint32_t))); + } + if (isValidGPIOpin(transmitPin) || (!m_oneWire && (transmitPin == 16))) { + m_txValid = true; + m_txPin = transmitPin; + } +} + +SoftwareSerialOld::~SoftwareSerialOld() { + enableRx(false); + if (m_rxValid) { + ObjList[m_rxPin] = 0; + } + if (m_buffer) { + free(m_buffer); + } +} + +bool SoftwareSerialOld::isValidGPIOpin(int pin) { + return (pin >= 0 && pin <= 5) || (pin >= 12 && pin <= MAX_PIN); +} + +void SoftwareSerialOld::begin(int32_t baud) { + m_bitCycles = ESP.getCpuFreqMHz() * 1000000 / baud; + m_intTxEnabled = true; + if (m_buffer != 0 && m_isrBuffer != 0) { + m_rxValid = true; + m_inPos = m_outPos = 0; + m_isrInPos.store(0); + m_isrOutPos.store(0); + pinMode(m_rxPin, INPUT_PULLUP); + if (this != ObjList[m_rxPin]) { delete ObjList[m_rxPin]; } + ObjList[m_rxPin] = this; + } + if (m_txValid && !m_oneWire) { +#ifdef ALT_DIGITAL_WRITE + digitalWrite(m_txPin, LOW); + pinMode(m_txPin, m_invert ? OUTPUT : INPUT_PULLUP); +#else + pinMode(m_txPin, OUTPUT); + digitalWrite(m_txPin, !m_invert); +#endif + } + + if (!m_rxEnabled) { enableRx(true); } +} + +int32_t SoftwareSerialOld::baudRate() { + return ESP.getCpuFreqMHz() * 1000000 / m_bitCycles; +} + +void SoftwareSerialOld::setTransmitEnablePin(int transmitEnablePin) { + if (isValidGPIOpin(transmitEnablePin)) { + m_txEnableValid = true; + m_txEnablePin = transmitEnablePin; +#ifdef ALT_DIGITAL_WRITE + digitalWrite(m_txEnablePin, LOW); + pinMode(m_txEnablePin, OUTPUT); +#else + pinMode(m_txEnablePin, OUTPUT); + digitalWrite(m_txEnablePin, LOW); +#endif + } else { + m_txEnableValid = false; + } +} + +void SoftwareSerialOld::enableIntTx(bool on) { + m_intTxEnabled = on; +} + +void SoftwareSerialOld::enableTx(bool on) { + if (m_oneWire && m_txValid) { + if (on) { + enableRx(false); +#ifdef ALT_DIGITAL_WRITE + digitalWrite(m_txPin, LOW); + pinMode(m_txPin, m_invert ? OUTPUT : INPUT_PULLUP); + digitalWrite(m_rxPin, LOW); + pinMode(m_rxPin, m_invert ? OUTPUT : INPUT_PULLUP); +#else + pinMode(m_txPin, OUTPUT); + digitalWrite(m_txPin, !m_invert); + pinMode(m_rxPin, OUTPUT); + digitalWrite(m_rxPin, !m_invert); +#endif + } else { +#ifdef ALT_DIGITAL_WRITE + digitalWrite(m_txPin, LOW); + pinMode(m_txPin, m_invert ? OUTPUT : INPUT_PULLUP); +#else + pinMode(m_txPin, OUTPUT); + digitalWrite(m_txPin, !m_invert); +#endif + pinMode(m_rxPin, INPUT_PULLUP); + enableRx(true); + } + } +} + +void SoftwareSerialOld::enableRx(bool on) { + if (m_rxValid) { + if (on) { + m_rxCurBit = 8; + attachInterrupt(digitalPinToInterrupt(m_rxPin), ISRList[m_rxPin], CHANGE); + } else { + detachInterrupt(digitalPinToInterrupt(m_rxPin)); + } + m_rxEnabled = on; + } +} + +int SoftwareSerialOld::read() { + if (!m_rxValid) { return -1; } + if (m_inPos == m_outPos) { + rxBits(); + if (m_inPos == m_outPos) { return -1; } + } + uint8_t ch = m_buffer[m_outPos]; + m_outPos = (m_outPos + 1) % m_bufSize; + return ch; +} + +int SoftwareSerialOld::available() { + if (!m_rxValid) { return 0; } + rxBits(); + int avail = m_inPos - m_outPos; + if (avail < 0) { avail += m_bufSize; } + if (!avail) { + optimistic_yield(20 * m_bitCycles / ESP.getCpuFreqMHz()); + rxBits(); + avail = m_inPos - m_outPos; + if (avail < 0) { avail += m_bufSize; } + } + return avail; +} + +void ICACHE_RAM_ATTR SoftwareSerialOld::preciseDelay(uint32_t deadline) { + int32_t micro_s = static_cast(deadline - ESP.getCycleCount()) / ESP.getCpuFreqMHz(); + // Reenable interrupts while delaying to avoid other tasks piling up + if (!m_intTxEnabled) { interrupts(); } + if (micro_s > 1) { + delayMicroseconds(micro_s - 1); + } + // Disable interrupts again + if (!m_intTxEnabled) { noInterrupts(); } + while (static_cast(deadline - ESP.getCycleCount()) > 1) {} +} + +void ICACHE_RAM_ATTR SoftwareSerialOld::writePeriod(uint32_t dutyCycle, uint32_t offCycle) { + if (dutyCycle) { + m_periodDeadline += dutyCycle; +#ifdef ALT_DIGITAL_WRITE + pinMode(m_txPin, INPUT_PULLUP); +#else + digitalWrite(m_txPin, HIGH); +#endif + preciseDelay(m_periodDeadline); + } + if (offCycle) { + m_periodDeadline += offCycle; +#ifdef ALT_DIGITAL_WRITE + pinMode(m_txPin, OUTPUT); +#else + digitalWrite(m_txPin, LOW); +#endif + preciseDelay(m_periodDeadline); + } +} + +size_t ICACHE_RAM_ATTR SoftwareSerialOld::write(uint8_t b) { + return write(&b, 1); +} + +size_t ICACHE_RAM_ATTR SoftwareSerialOld::write(const uint8_t *buffer, size_t size) { + if (m_rxValid) { rxBits(); } + if (!m_txValid) { return 0; } + + if (m_txEnableValid) { +#ifdef ALT_DIGITAL_WRITE + pinMode(m_txEnablePin, INPUT_PULLUP); +#else + digitalWrite(m_txEnablePin, HIGH); +#endif + } + // Stop bit level : LOW if inverted logic, otherwise HIGH +#ifdef ALT_DIGITAL_WRITE + pinMode(m_txPin, m_invert ? OUTPUT : INPUT_PULLUP); +#else + digitalWrite(m_txPin, !m_invert); +#endif + uint32_t dutyCycle = 0; + uint32_t offCycle = 0; + bool pb; + // Disable interrupts in order to get a clean transmit timing + if (!m_intTxEnabled) { noInterrupts(); } + m_periodDeadline = ESP.getCycleCount(); + for (int cnt = 0; cnt < size; ++cnt, ++buffer) { + // Start bit : HIGH if inverted logic, otherwise LOW + if (m_invert) { dutyCycle += m_bitCycles; } else { offCycle += m_bitCycles; } + pb = m_invert; + uint8_t o = m_invert ? ~*buffer : *buffer; + bool b; + for (int i = 0; i < 9; ++i) { + // data bit + // or stop bit : LOW if inverted logic, otherwise HIGH + b = (i < 8) ? (o & 1) : !m_invert; + o >>= 1; + if (!pb && b) { + writePeriod(dutyCycle, offCycle); + dutyCycle = offCycle = 0; + } + if (b) { dutyCycle += m_bitCycles; } else { offCycle += m_bitCycles; } + pb = b; + } + if (cnt == size - 1) { + writePeriod(dutyCycle, offCycle); + break; + } + } + if (!m_intTxEnabled) { interrupts(); } + if (m_txEnableValid) { +#ifdef ALT_DIGITAL_WRITE + pinMode(m_txEnablePin, OUTPUT); +#else + digitalWrite(m_txEnablePin, LOW); +#endif + } + return size; +} + +void SoftwareSerialOld::flush() { + m_inPos = m_outPos = 0; + m_isrInPos.store(0); + m_isrOutPos.store(0); +} + +bool SoftwareSerialOld::overflow() { + bool res = m_overflow; + m_overflow = false; + return res; +} + +int SoftwareSerialOld::peek() { + if (!m_rxValid || (rxBits(), m_inPos == m_outPos)) { return -1; } + return m_buffer[m_outPos]; +} + +void ICACHE_RAM_ATTR SoftwareSerialOld::rxBits() { + int avail = m_isrInPos.load() - m_isrOutPos.load(); + if (avail < 0) { avail += m_isrBufSize; } + if (m_isrOverflow.load()) { + m_overflow = true; + m_isrOverflow.store(false); + } + + // stop bit can go undetected if leading data bits are at same level + // and there was also no next start bit yet, so one byte may be pending. + // low-cost check first + if (avail == 0 && m_rxCurBit < 8 && m_isrInPos.load() == m_isrOutPos.load() && m_rxCurBit >= 0) { + uint32_t delta = ESP.getCycleCount() - m_isrLastCycle.load(); + uint32_t expectedDelta = (10 - m_rxCurBit) * m_bitCycles; + if (delta >= expectedDelta) { + // Store inverted stop bit edge and cycle in the buffer unless we have an overflow + // cycle's LSB is repurposed for the level bit + int next = (m_isrInPos.load() + 1) % m_isrBufSize; + if (next != m_isrOutPos.load()) { + uint32_t expectedCycle = m_isrLastCycle.load() + expectedDelta; + m_isrBuffer[m_isrInPos.load()].store((expectedCycle | 1) ^ !m_invert); + m_isrInPos.store(next); + ++avail; + } else { + m_isrOverflow.store(true); + } + } + } + + while (avail--) { + // error introduced by edge value in LSB is neglegible + uint32_t isrCycle = m_isrBuffer[m_isrOutPos.load()].load(); + // extract inverted edge value + bool level = (isrCycle & 1) == m_invert; + m_isrOutPos.store((m_isrOutPos.load() + 1) % m_isrBufSize); + int32_t cycles = static_cast(isrCycle - m_isrLastCycle.load()) - (m_bitCycles / 2); + if (cycles < 0) { continue; } + m_isrLastCycle.store(isrCycle); + do { + // data bits + if (m_rxCurBit >= -1 && m_rxCurBit < 7) { + if (cycles >= m_bitCycles) { + // preceding masked bits + int hiddenBits = cycles / m_bitCycles; + if (hiddenBits > 7 - m_rxCurBit) { hiddenBits = 7 - m_rxCurBit; } + bool lastBit = m_rxCurByte & 0x80; + m_rxCurByte >>= hiddenBits; + // masked bits have same level as last unmasked bit + if (lastBit) { m_rxCurByte |= 0xff << (8 - hiddenBits); } + m_rxCurBit += hiddenBits; + cycles -= hiddenBits * m_bitCycles; + } + if (m_rxCurBit < 7) { + ++m_rxCurBit; + cycles -= m_bitCycles; + m_rxCurByte >>= 1; + if (level) { m_rxCurByte |= 0x80; } + } + continue; + } + if (m_rxCurBit == 7) { + m_rxCurBit = 8; + cycles -= m_bitCycles; + // Store the received value in the buffer unless we have an overflow + int next = (m_inPos + 1) % m_bufSize; + if (next != m_outPos) { + m_buffer[m_inPos] = m_rxCurByte; + // reset to 0 is important for masked bit logic + m_rxCurByte = 0; + m_inPos = next; + } else { + m_overflow = true; + } + continue; + } + if (m_rxCurBit == 8) { + // start bit level is low + if (!level) { + m_rxCurBit = -1; + } + } + break; + } while (cycles >= 0); + } +} + +void ICACHE_RAM_ATTR SoftwareSerialOld::rxRead() { + uint32_t curCycle = ESP.getCycleCount(); + bool level = digitalRead(m_rxPin); + + // Store inverted edge value & cycle in the buffer unless we have an overflow + // cycle's LSB is repurposed for the level bit + int next = (m_isrInPos.load() + 1) % m_isrBufSize; + if (next != m_isrOutPos.load()) { + m_isrBuffer[m_isrInPos.load()].store((curCycle | 1) ^ level); + m_isrInPos.store(next); + } else { + m_isrOverflow.store(true); + } +} + +void SoftwareSerialOld::onReceive(std::function handler) { + receiveHandler = handler; +} + +void SoftwareSerialOld::perform_work() { + if (receiveHandler) { + if (!m_rxValid) { return; } + rxBits(); + int avail = m_inPos - m_outPos; + if (avail < 0) { avail += m_bufSize; } + if (avail) { receiveHandler(avail); } + } +} diff --git a/temp/SoftwareSerialOld.h b/temp/SoftwareSerialOld.h new file mode 100644 index 0000000..269b427 --- /dev/null +++ b/temp/SoftwareSerialOld.h @@ -0,0 +1,117 @@ +/* +SoftwareSerialOld.h + +SoftwareSerialOld.cpp - Implementation of the Arduino software serial for ESP8266/ESP32. +Copyright (c) 2015-2016 Peter Lerup. All rights reserved. +Copyright (c) 2018-2019 Dirk O. Kaar. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef SoftwareSerialOld_h +#define SoftwareSerialOld_h + +#include +#include +#include +#include + +// If only one tx or rx wanted then use this as parameter for the unused pin +constexpr int SW_SERIAL_UNUSED_PIN = -1; + +// This class is compatible with the corresponding AVR one, +// the constructor however has an optional rx buffer size. +// Baudrates up to 115200 can be used. + + +class SoftwareSerialOld : public Stream { +public: + SoftwareSerialOld(int receivePin, int transmitPin, bool inverse_logic = false, int bufSize = 64, int isrBufSize = 0); + virtual ~SoftwareSerialOld(); + + void begin(int32_t baud); + int32_t baudRate(); + // Transmit control pin + void setTransmitEnablePin(int transmitEnablePin); + // Enable or disable interrupts during tx + void enableIntTx(bool on); + + bool overflow(); + + int available() override; + int peek() override; + int read() override; + void flush() override; + size_t write(uint8_t byte) override; + size_t write(const uint8_t *buffer, size_t size) override; + operator bool() const { return m_rxValid || m_txValid; } + + // Disable or enable interrupts on the rx pin + void enableRx(bool on); + // One wire control + void enableTx(bool on); + + void rxRead(); + + // AVR compatibility methods + bool listen() { enableRx(true); return true; } + void end() { stopListening(); } + bool isListening() { return m_rxEnabled; } + bool stopListening() { enableRx(false); return true; } + + void onReceive(std::function handler); + void perform_work(); + + using Print::write; + +private: + void preciseDelay(uint32_t deadline); + void writePeriod(uint32_t dutyCycle, uint32_t offCycle); + bool isValidGPIOpin(int pin); + /* check m_rxValid that calling is safe */ + void rxBits(); + + // Member variables + bool m_oneWire; + int m_rxPin = SW_SERIAL_UNUSED_PIN; + int m_txPin = SW_SERIAL_UNUSED_PIN; + int m_txEnablePin = SW_SERIAL_UNUSED_PIN; + bool m_rxValid = false; + bool m_rxEnabled = false; + bool m_txValid = false; + bool m_txEnableValid = false; + bool m_invert; + bool m_overflow = false; + int32_t m_bitCycles; + uint32_t m_periodDeadline; + bool m_intTxEnabled; + int m_inPos, m_outPos; + int m_bufSize = 0; + uint8_t *m_buffer = 0; + // the ISR stores the relative bit times in the buffer. The inversion corrected level is used as sign bit (2's complement): + // 1 = positive including 0, 0 = negative. + std::atomic m_isrInPos, m_isrOutPos; + int m_isrBufSize = 0; + std::atomic* m_isrBuffer; + std::atomic m_isrOverflow; + std::atomic m_isrLastCycle; + int m_rxCurBit; // 0 - 7: data bits. -1: start bit. 8: stop bit. + uint8_t m_rxCurByte = 0; + + std::function receiveHandler = 0; +}; + +#endif diff --git a/temp/SoftwareSerialOriginal.cpp b/temp/SoftwareSerialOriginal.cpp new file mode 100644 index 0000000..013c9bf --- /dev/null +++ b/temp/SoftwareSerialOriginal.cpp @@ -0,0 +1,250 @@ +/* + +SoftwareSerialOriginal.cpp - Implementation of the Arduino software serial for ESP8266. +Copyright (c) 2015-2016 Peter Lerup. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include + +// The Arduino standard GPIO routines are not enough, +// must use some from the Espressif SDK as well +extern "C" { +#include "gpio.h" +} + +#include "SoftwareSerialOriginal.h" + +#define MAX_PIN 15 + +// As the Arduino attachInterrupt has no parameter, lists of objects +// and callbacks corresponding to each possible GPIO pins have to be defined +SoftwareSerialOriginal *ObjList[MAX_PIN+1]; + +void ICACHE_RAM_ATTR sws_isr_0() { ObjList[0]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_1() { ObjList[1]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_2() { ObjList[2]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_3() { ObjList[3]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_4() { ObjList[4]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_5() { ObjList[5]->rxRead(); }; +// Pin 6 to 11 can not be used +void ICACHE_RAM_ATTR sws_isr_12() { ObjList[12]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_13() { ObjList[13]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_14() { ObjList[14]->rxRead(); }; +void ICACHE_RAM_ATTR sws_isr_15() { ObjList[15]->rxRead(); }; + +static void (*ISRList[MAX_PIN+1])() = { + sws_isr_0, + sws_isr_1, + sws_isr_2, + sws_isr_3, + sws_isr_4, + sws_isr_5, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + sws_isr_12, + sws_isr_13, + sws_isr_14, + sws_isr_15 +}; + +SoftwareSerialOriginal::SoftwareSerialOriginal(int receivePin, int transmitPin, bool inverse_logic, unsigned int buffSize) { + m_oneWire = (receivePin == transmitPin); + m_rxValid = m_txValid = m_txEnableValid = false; + m_buffer = NULL; + m_invert = inverse_logic; + m_overflow = false; + m_rxEnabled = false; + if (isValidGPIOpin(receivePin)) { + m_rxPin = receivePin; + m_buffSize = buffSize; + m_buffer = (uint8_t*)malloc(m_buffSize); + if (m_buffer != NULL) { + m_rxValid = true; + m_inPos = m_outPos = 0; + pinMode(m_rxPin, INPUT); + ObjList[m_rxPin] = this; + } + } + if (isValidGPIOpin(transmitPin) || (!m_oneWire && (transmitPin == 16))) { + m_txValid = true; + m_txPin = transmitPin; + if (!m_oneWire) { + pinMode(m_txPin, OUTPUT); + digitalWrite(m_txPin, !m_invert); + } + } + // Default speed + begin(9600); +} + +SoftwareSerialOriginal::~SoftwareSerialOriginal() { + enableRx(false); + if (m_rxValid) + ObjList[m_rxPin] = NULL; + if (m_buffer) + free(m_buffer); +} + +bool SoftwareSerialOriginal::isValidGPIOpin(int pin) { + return (pin >= 0 && pin <= 5) || (pin >= 12 && pin <= MAX_PIN); +} + +void SoftwareSerialOriginal::begin(long speed) { + // Use getCycleCount() loop to get as exact timing as possible + m_bitTime = ESP.getCpuFreqMHz()*1000000/speed; + // By default enable interrupt during tx only for low speed + m_intTxEnabled = speed < 9600; + + if (!m_rxEnabled) + enableRx(true); +} + +long SoftwareSerialOriginal::baudRate() { + return ESP.getCpuFreqMHz()*1000000/m_bitTime; +} + +void SoftwareSerialOriginal::setTransmitEnablePin(int transmitEnablePin) { + if (isValidGPIOpin(transmitEnablePin)) { + m_txEnableValid = true; + m_txEnablePin = transmitEnablePin; + pinMode(m_txEnablePin, OUTPUT); + digitalWrite(m_txEnablePin, LOW); + } else { + m_txEnableValid = false; + } +} + +void SoftwareSerialOriginal::enableIntTx(bool on) { + m_intTxEnabled = on; +} + +void SoftwareSerialOriginal::enableTx(bool on) { + if (m_oneWire && m_txValid) { + if (on) { + enableRx(false); + digitalWrite(m_txPin, !m_invert); + pinMode(m_rxPin, OUTPUT); + } else { + digitalWrite(m_txPin, !m_invert); + pinMode(m_rxPin, INPUT); + enableRx(true); + } + delay(1); // it's important to have a delay after switching + } +} + +void SoftwareSerialOriginal::enableRx(bool on) { + if (m_rxValid) { + if (on) + attachInterrupt(m_rxPin, ISRList[m_rxPin], m_invert ? RISING : FALLING); + else + detachInterrupt(m_rxPin); + m_rxEnabled = on; + } +} + +int SoftwareSerialOriginal::read() { + if (!m_rxValid || (m_inPos == m_outPos)) return -1; + uint8_t ch = m_buffer[m_outPos]; + m_outPos = (m_outPos+1) % m_buffSize; + return ch; +} + +int SoftwareSerialOriginal::available() { + if (!m_rxValid) return 0; + int avail = m_inPos - m_outPos; + if (avail < 0) avail += m_buffSize; + return avail; +} + +#define WAIT { while (ESP.getCycleCount()-start < wait) if (m_intTxEnabled) optimistic_yield(1); wait += m_bitTime; } + +size_t SoftwareSerialOriginal::write(uint8_t b) { + if (!m_txValid) return 0; + + if (m_invert) b = ~b; + if (!m_intTxEnabled) + // Disable interrupts in order to get a clean transmit + cli(); + if (m_txEnableValid) digitalWrite(m_txEnablePin, HIGH); + unsigned long wait = m_bitTime; + digitalWrite(m_txPin, HIGH); + unsigned long start = ESP.getCycleCount(); + // Start bit; + digitalWrite(m_txPin, LOW); + WAIT; + for (int i = 0; i < 8; i++) { + digitalWrite(m_txPin, (b & 1) ? HIGH : LOW); + WAIT; + b >>= 1; + } + // Stop bit + digitalWrite(m_txPin, HIGH); + WAIT; + if (m_txEnableValid) digitalWrite(m_txEnablePin, LOW); + if (!m_intTxEnabled) + sei(); + return 1; +} + +void SoftwareSerialOriginal::flush() { + m_inPos = m_outPos = 0; +} + +bool SoftwareSerialOriginal::overflow() { + bool res = m_overflow; + m_overflow = false; + return res; +} + +int SoftwareSerialOriginal::peek() { + if (!m_rxValid || (m_inPos == m_outPos)) return -1; + return m_buffer[m_outPos]; +} + +void ICACHE_RAM_ATTR SoftwareSerialOriginal::rxRead() { + // Advance the starting point for the samples but compensate for the + // initial delay which occurs before the interrupt is delivered + unsigned long wait = m_bitTime + m_bitTime/3 - 500; + unsigned long start = ESP.getCycleCount(); + uint8_t rec = 0; + for (int i = 0; i < 8; i++) { + WAIT; + rec >>= 1; + if (digitalRead(m_rxPin)) + rec |= 0x80; + } + if (m_invert) rec = ~rec; + // Stop bit + WAIT; + // Store the received value in the buffer unless we have an overflow + int next = (m_inPos+1) % m_buffSize; + if (next != m_outPos) { + m_buffer[m_inPos] = rec; + m_inPos = next; + } else { + m_overflow = true; + } + // Must clear this bit in the interrupt register, + // it gets set even when interrupts are disabled + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << m_rxPin); +} diff --git a/temp/SoftwareSerialOriginal.h b/temp/SoftwareSerialOriginal.h new file mode 100644 index 0000000..1c90577 --- /dev/null +++ b/temp/SoftwareSerialOriginal.h @@ -0,0 +1,93 @@ +/* +SoftwareSerialOriginal.h + +SoftwareSerialOriginal.cpp - Implementation of the Arduino software serial for ESP8266. +Copyright (c) 2015-2016 Peter Lerup. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef SoftwareSerialOriginal_h +#define SoftwareSerialOriginal_h + +#include +#include + + +// This class is compatible with the corresponding AVR one, +// the constructor however has an optional rx buffer size. +// Speed up to 115200 can be used. + + +class SoftwareSerialOriginal : public Stream { +public: + SoftwareSerialOriginal(int receivePin, int transmitPin, bool inverse_logic = false, unsigned int buffSize = 64); + ~SoftwareSerialOriginal(); + + void begin(long speed); + long baudRate(); + // Transmit control pin + void setTransmitEnablePin(int transmitEnablePin); + // Enable or disable interrupts during tx + void enableIntTx(bool on); + + bool overflow(); + int peek(); + + virtual size_t write(uint8_t byte); + virtual int read(); + virtual int available(); + virtual void flush(); + operator bool() {return m_rxValid || m_txValid;} + + // Disable or enable interrupts on the rx pin + void enableRx(bool on); + // One wire control + void enableTx(bool on); + + void rxRead(); + + // AVR compatibility methods + bool listen() { enableRx(true); return true; } + void end() { stopListening(); } + bool isListening() { return m_rxEnabled; } + bool stopListening() { enableRx(false); return true; } + + using Print::write; + +private: + bool isValidGPIOpin(int pin); + + // Member variables + bool m_oneWire; + int m_rxPin, m_txPin, m_txEnablePin; + bool m_rxValid, m_rxEnabled; + bool m_txValid, m_txEnableValid; + bool m_invert; + bool m_overflow; + unsigned long m_bitTime; + bool m_intTxEnabled; + unsigned int m_inPos, m_outPos; + int m_buffSize; + uint8_t *m_buffer; + +}; + +// If only one tx or rx wanted then use this as parameter for the unused pin +#define SW_SERIAL_UNUSED_PIN -1 + + +#endif