diff --git a/lib/obp60task/GwOBP60Task.cpp b/lib/obp60task/GwOBP60Task.cpp deleted file mode 100644 index 7a9d154..0000000 --- a/lib/obp60task/GwOBP60Task.cpp +++ /dev/null @@ -1,582 +0,0 @@ - -//we only compile for some boards -#ifdef BOARD_NODEMCU32S_OBP60 -#include "GwOBP60Task.h" -#include "GwApi.h" -#include "OBP60Hardware.h" // PIN definitions -#include // Timer Lib for timer interrupts -#include // I2C connections -#include // MCP23017 extension Port -#include -#include -#include -#include // GxEPD lib for E-Ink displays -#include // 4.2" Waveshare S/W 300 x 400 pixel -#include // GxEPD lip for SPI display communikation -#include // GxEPD lip for SPI -#include "OBP60ExtensionPort.h" // Functions lib for extension board -#include "OBP60Keypad.h" // Functions lib for keypad - -// True type character sets -#include "Ubuntu_Bold8pt7b.h" -#include "Ubuntu_Bold20pt7b.h" -#include "Ubuntu_Bold32pt7b.h" -#include "DSEG7Classic-BoldItalic16pt7b.h" -#include "DSEG7Classic-BoldItalic42pt7b.h" -#include "DSEG7Classic-BoldItalic60pt7b.h" - -// Pictures -//#include GxEPD_BitmapExamples // Example picture -#include "MFD_OBP60_400x300_sw.h" // MFD with logo -#include "Logo_OBP_400x300_sw.h" // OBP Logo - -#include "OBP60Data.h" // Data stucture -#include "OBP60QRWiFi.h" // Functions lib for WiFi QR code -#include "Page_0.h" // Page 0 Depth -#include "Page_1.h" // Page 1 Speed -#include "Page_2.h" // Page 2 VBat -#include "Page_3.h" // Page 3 Depht / Speed -#include "OBP60Pages.h" // Functions lib for show pages -// new comment Adrien - -tNMEA0183Msg NMEA0183Msg; -tNMEA0183 NMEA0183; - -// Timer Interrupts for hardware functions -Ticker Timer1; // Under voltage detection -Ticker Timer2; // Keypad -Ticker Timer3; // Binking flash LED - -// Global vars -bool initComplete = false; // Initialization complete -int taskRunCounter = 0; // Task couter for loop section -bool gps_ready = false; // GPS initialized and ready to use - -#define INVALID_COORD -99999 -class GetBoatDataRequest: public GwMessage{ - private: - GwApi *api; - public: - double latitude; - double longitude; - GetBoatDataRequest(GwApi *api):GwMessage(F("boat data")){ - this->api=api; - } - virtual ~GetBoatDataRequest(){} - protected: - /** - * this methos will be executed within the main thread - * be sure not to make any time consuming or blocking operation - */ - virtual void processImpl(){ - //api->getLogger()->logDebug(GwLog::DEBUG,"boatData request from example task"); - /*access the values from boatData (see GwBoatData.h) - by using getDataWithDefault it will return the given default value - if there is no valid data available - so be sure to use a value that never will be a valid one - alternatively you can check using the isValid() method at each boatData item - */ - latitude=api->getBoatData()->Latitude->getDataWithDefault(INVALID_COORD); - longitude=api->getBoatData()->Longitude->getDataWithDefault(INVALID_COORD); - }; -}; - -String formatValue(GwApi::BoatValue *value){ - if (! value->valid) return "----"; - static const int bsize=30; - char buffer[bsize+1]; - buffer[0]=0; - if (value->getFormat() == "formatDate"){ - time_t tv=tNMEA0183Msg::daysToTime_t(value->value); - tmElements_t parts; - tNMEA0183Msg::breakTime(tv,parts); - snprintf(buffer,bsize,"%02d.%02d.%04d",parts.tm_mday,parts.tm_mon+1,parts.tm_year+1900); - } - else if(value->getFormat() == "formatTime"){ - double inthr; - double intmin; - double intsec; - double val; - val=modf(value->value/3600.0,&inthr); - val=modf(val*3600.0/60.0,&intmin); - modf(val*60.0,&intsec); -// snprintf(buffer,bsize,"%02.0f:%02.0f:%02.0f",inthr,intmin,intsec); - snprintf(buffer,bsize,"%02.0f:%02.0f",inthr,intmin); - } - else if (value->getFormat() == "formatFixed0"){ - snprintf(buffer,bsize,"%.0f",value->value); - } - else{ - snprintf(buffer,bsize,"%.4f",value->value); - } - buffer[bsize]=0; - return String(buffer); -} - -// Hardware initialisation before start all services -//################################################## -void OBP60Init(GwApi *api){ - GwLog *logger=api->getLogger(); - - // Define timer interrupts - bool uvoltage = api->getConfig()->getConfigItem(api->getConfig()->underVoltage,true)->asBoolean(); - if(uvoltage == true){ - Timer1.attach_ms(1, underVoltageDetection); // Maximum speed with 1ms - } - Timer2.attach_ms(40, readKeypad); // Timer value nust grater than 30ms - Timer3.attach_ms(500, blinkingFlashLED); - - // Extension port MCP23017 - // Check I2C devices MCP23017 - Wire.begin(OBP_I2C_SDA, OBP_I2C_SCL); - Wire.beginTransmission(MCP23017_I2C_ADDR); - if (Wire.endTransmission() != 0) { - LOG_DEBUG(GwLog::ERROR,"MCP23017 not found, check wiring"); - initComplete = false; - } - else{ - // Start communication - mcp.init(); - mcp.portMode(MCP23017Port::A, 0b00110000); //Port A, 0 = out, 1 = in - mcp.portMode(MCP23017Port::B, 0b11110000); //Port B, 0 = out, 1 = in - - // Extension Port A set defaults - setPortPin(OBP_DIGITAL_OUT1, false); // PA0 - setPortPin(OBP_DIGITAL_OUT2, false); // PA1 - setPortPin(OBP_FLASH_LED, false); // PA2 - setPortPin(OBP_BACKLIGHT_LED, false); // PA3 - setPortPin(OBP_POWER_50, true); // PA6 - setPortPin(OBP_POWER_33, true); // PA7 - - // Extension Port B set defaults - setPortPin(PB0, false); // PB0 - setPortPin(PB1, false); // PB1 - setPortPin(PB2, false); // PB2 - setPortPin(PB3, false); // PB3 - - // Settings for 1Wire - bool enable1Wire = api->getConfig()->getConfigItem(api->getConfig()->use1Wire,true)->asBoolean(); - if(enable1Wire == true){ - LOG_DEBUG(GwLog::DEBUG,"1Wire Mode is On"); - } - else{ - LOG_DEBUG(GwLog::DEBUG,"1Wire Mode is Off"); - } - - // Settings for NMEA0183 - String nmea0183Mode = api->getConfig()->getConfigItem(api->getConfig()->serialDirection,true)->asString(); - LOG_DEBUG(GwLog::DEBUG,"NMEA0183 Mode is: %s", nmea0183Mode); - pinMode(OBP_DIRECTION_PIN, OUTPUT); - if(String(nmea0183Mode) == "receive" || String(nmea0183Mode) == "off"){ - digitalWrite(OBP_DIRECTION_PIN, false); - } - if(String(nmea0183Mode) == "send"){ - digitalWrite(OBP_DIRECTION_PIN, true); - } - - // Settings for backlight - String backlightMode = api->getConfig()->getConfigItem(api->getConfig()->backlight,true)->asString(); - LOG_DEBUG(GwLog::DEBUG,"Backlight Mode is: %s", backlightMode); - if(String(backlightMode) == "On"){ - setPortPin(OBP_BACKLIGHT_LED, true); - } - if(String(backlightMode) == "Off"){ - setPortPin(OBP_BACKLIGHT_LED, false); - } - if(String(backlightMode) == "Control by Key"){ - setPortPin(OBP_BACKLIGHT_LED, false); - } - - // Settings flash LED mode - String ledMode = api->getConfig()->getConfigItem(api->getConfig()->flashLED,true)->asString(); - LOG_DEBUG(GwLog::DEBUG,"Backlight Mode is: %s", ledMode); - if(String(ledMode) == "Off"){ - blinkingLED = false; - } - if(String(ledMode) == "Limits Overrun"){ - blinkingLED = true; - } - - // Start serial stream and take over GPS data stream form internal GPS - bool gpsOn=api->getConfig()->getConfigItem(api->getConfig()->useGPS,true)->asBoolean(); - if(gpsOn == true){ - Serial2.begin(9600, SERIAL_8N1, OBP_GPS_TX, -1); // GPS RX unused in hardware (-1) - if (!Serial2) { - LOG_DEBUG(GwLog::ERROR,"GPS modul NEO-6M not found, check wiring"); - gps_ready = false; - } - else{ - LOG_DEBUG(GwLog::DEBUG,"GPS modul NEO-M6 found"); - NMEA0183.SetMessageStream(&Serial2); - NMEA0183.Open(); - gps_ready = true; - } - } - - // Marker for init complete - // Used in OBP60Task() - initComplete = true; - - // Buzzer tone for initialization finish - buzPower = uint(api->getConfig()->getConfigItem(api->getConfig()->buzzerPower,true)->asInt()); - buzzer(TONE4, buzPower, 500); - - } -} - -// OBP60 Task -//####################################### -void OBP60Task(void *param){ - - GwApi *api=(GwApi*)param; - GwLog *logger=api->getLogger(); - GwApi::Status status; - - bool hasPosition = false; - - // Get configuration data from webside - // System Settings - api->getConfig()->getConfigItem(api->getConfig()->systemName,true)->asString().toCharArray(busInfo.systemname, 32); - api->getConfig()->getConfigItem(api->getConfig()->systemName,true)->asString().toCharArray(busInfo.wifissid, 32); - api->getConfig()->getConfigItem(api->getConfig()->apPassword,true)->asString().toCharArray(busInfo.wifipass, 32); - busInfo.useadminpass = api->getConfig()->getConfigItem(api->getConfig()->useAdminPass,true)->asBoolean(); - api->getConfig()->getConfigItem(api->getConfig()->adminPassword,true)->asString().toCharArray(busInfo.adminpassword, 32); - api->getConfig()->getConfigItem(api->getConfig()->logLevel,true)->asString().toCharArray(busInfo.loglevel, 16); - // WiFi client settings - busInfo.wificlienton = api->getConfig()->getConfigItem(api->getConfig()->wifiClient,true)->asBoolean(); - api->getConfig()->getConfigItem(api->getConfig()->wifiSSID,true)->asString().toCharArray(busInfo.wificlientssid, 32); - api->getConfig()->getConfigItem(api->getConfig()->wifiPass,true)->asString().toCharArray(busInfo.wificlientpass, 32); - // OBP60 Settings - bool exampleSwitch = api->getConfig()->getConfigItem(api->getConfig()->obp60Config,true)->asBoolean(); - LOG_DEBUG(GwLog::DEBUG,"example switch ist %s",exampleSwitch?"true":"false"); - api->getConfig()->getConfigItem(api->getConfig()->lengthFormat,true)->asString().toCharArray(busInfo.lengthformat, 16); - api->getConfig()->getConfigItem(api->getConfig()->distanceFormat,true)->asString().toCharArray(busInfo.distanceformat, 16); - api->getConfig()->getConfigItem(api->getConfig()->speedFormat,true)->asString().toCharArray(busInfo.speedformat, 16); - api->getConfig()->getConfigItem(api->getConfig()->windspeedFormat,true)->asString().toCharArray(busInfo.windspeedformat, 16); - api->getConfig()->getConfigItem(api->getConfig()->tempFormat,true)->asString().toCharArray(busInfo.tempformat, 16); - api->getConfig()->getConfigItem(api->getConfig()->dateFormat,true)->asString().toCharArray(busInfo.dateformat, 3); - busInfo.timezone = api->getConfig()->getConfigItem(api->getConfig()->timeZone,true)->asInt(); - busInfo.draft = api->getConfig()->getConfigItem(api->getConfig()->draft,true)->asString().toFloat(); - busInfo.fueltank = api->getConfig()->getConfigItem(api->getConfig()->fuelTank,true)->asString().toFloat(); - busInfo.fuelconsumption = api->getConfig()->getConfigItem(api->getConfig()->fuelConsumption,true)->asString().toFloat(); - busInfo.watertank = api->getConfig()->getConfigItem(api->getConfig()->waterTank,true)->asString().toFloat(); - busInfo.wastetank = api->getConfig()->getConfigItem(api->getConfig()->wasteTank,true)->asString().toFloat(); - busInfo.batvoltage = api->getConfig()->getConfigItem(api->getConfig()->batteryVoltage,true)->asString().toFloat(); - api->getConfig()->getConfigItem(api->getConfig()->batteryType,true)->asString().toCharArray(busInfo.battype, 16); - busInfo.batcapacity = api->getConfig()->getConfigItem(api->getConfig()->batteryCapacity,true)->asString().toFloat(); - // OBP60 Hardware - busInfo.gps = api->getConfig()->getConfigItem(api->getConfig()->useGPS,true)->asBoolean(); - busInfo.bme280 = api->getConfig()->getConfigItem(api->getConfig()->useBME280,true)->asBoolean(); - busInfo.onewire = api->getConfig()->getConfigItem(api->getConfig()->use1Wire,true)->asBoolean(); - api->getConfig()->getConfigItem(api->getConfig()->powerMode,true)->asString().toCharArray(busInfo.powermode, 16); - busInfo.simulation = api->getConfig()->getConfigItem(api->getConfig()->useSimuData,true)->asBoolean(); - // OBP60 Display - api->getConfig()->getConfigItem(api->getConfig()->display,true)->asString().toCharArray(busInfo.displaymode, 16); - busInfo.statusline = api->getConfig()->getConfigItem(api->getConfig()->statusLine,true)->asBoolean(); - busInfo.refresh = api->getConfig()->getConfigItem(api->getConfig()->refresh,true)->asBoolean(); - busInfo.holdvalues = api->getConfig()->getConfigItem(api->getConfig()->holdvalues,true)->asBoolean(); - api->getConfig()->getConfigItem(api->getConfig()->backlight,true)->asString().toCharArray(busInfo.backlight, 16); - api->getConfig()->getConfigItem(api->getConfig()->powerMode,true)->asString().toCharArray(busInfo.powermode, 16); - // OBP60 Buzzer - busInfo.buzerror = api->getConfig()->getConfigItem(api->getConfig()->buzzerError,true)->asBoolean(); - busInfo.buzgps = api->getConfig()->getConfigItem(api->getConfig()->buzzerGps,true)->asBoolean(); - busInfo.buzlimits = api->getConfig()->getConfigItem(api->getConfig()->buzzerLim,true)->asBoolean(); - api->getConfig()->getConfigItem(api->getConfig()->buzzerMode,true)->asString().toCharArray(busInfo.buzmode, 16); - busInfo.buzpower = api->getConfig()->getConfigItem(api->getConfig()->buzzerPower,true)->asInt(); - // OBP60 Pages - busInfo.numpages = api->getConfig()->getConfigItem(api->getConfig()->numberPages,true)->asInt(); - - // Initializing all necessary boat data - GwApi::BoatValue *cog=new GwApi::BoatValue(F("COG")); - GwApi::BoatValue *twd=new GwApi::BoatValue(F("TWD")); - GwApi::BoatValue *awd=new GwApi::BoatValue(F("AWD")); - GwApi::BoatValue *sog=new GwApi::BoatValue(F("SOG")); - GwApi::BoatValue *stw=new GwApi::BoatValue(F("STW")); - GwApi::BoatValue *tws=new GwApi::BoatValue(F("TWS")); - GwApi::BoatValue *aws=new GwApi::BoatValue(F("AWS")); - GwApi::BoatValue *maxtws=new GwApi::BoatValue(F("MaxTws")); - GwApi::BoatValue *maxaws=new GwApi::BoatValue(F("MaxAws")); - GwApi::BoatValue *awa=new GwApi::BoatValue(F("AWA")); - GwApi::BoatValue *heading=new GwApi::BoatValue(F("Heading")); - GwApi::BoatValue *mheading=new GwApi::BoatValue(F("MagneticHeading")); - GwApi::BoatValue *rot=new GwApi::BoatValue(F("ROT")); - GwApi::BoatValue *variation=new GwApi::BoatValue(F("Variation")); - GwApi::BoatValue *hdop=new GwApi::BoatValue(F("HDOP")); - GwApi::BoatValue *pdop=new GwApi::BoatValue(F("PDOP")); - GwApi::BoatValue *vdop=new GwApi::BoatValue(F("VDOP")); - GwApi::BoatValue *rudderpos=new GwApi::BoatValue(F("RudderPosition")); - GwApi::BoatValue *latitude=new GwApi::BoatValue(F("Latitude")); - GwApi::BoatValue *longitude=new GwApi::BoatValue(F("Longitude")); - GwApi::BoatValue *altitude=new GwApi::BoatValue(F("Altitude")); - GwApi::BoatValue *waterdepth=new GwApi::BoatValue(F("WaterDepth")); - GwApi::BoatValue *depthtransducer=new GwApi::BoatValue(F("DepthTransducer")); - GwApi::BoatValue *time=new GwApi::BoatValue(F("GpsTime")); - GwApi::BoatValue *date=new GwApi::BoatValue(F("GpsDate")); - GwApi::BoatValue *timezone=new GwApi::BoatValue(F("Timezone")); - GwApi::BoatValue *satinfo=new GwApi::BoatValue(F("SatInfo")); - GwApi::BoatValue *watertemp=new GwApi::BoatValue(F("WaterTemperature")); - GwApi::BoatValue *xte=new GwApi::BoatValue(F("XTE")); - GwApi::BoatValue *dtw=new GwApi::BoatValue(F("DTW")); - GwApi::BoatValue *btw=new GwApi::BoatValue(F("BTW")); - GwApi::BoatValue *wplatitude=new GwApi::BoatValue(F("WPLatitude")); - GwApi::BoatValue *wplongitude=new GwApi::BoatValue(F("WPLongitude")); - GwApi::BoatValue *log=new GwApi::BoatValue(F("Log")); - GwApi::BoatValue *triplog=new GwApi::BoatValue(F("TripLog")); - - //################################################################# - - GwApi::BoatValue *valueList[]={cog, twd, awd, sog, stw, tws, aws, maxtws, maxaws, awa, heading, mheading, rot, variation, hdop, pdop, vdop, rudderpos, latitude, longitude, altitude, waterdepth, depthtransducer, time, date, timezone, satinfo, watertemp, xte, dtw, btw, wplatitude, wplongitude, log, triplog}; - - //Init E-Ink display - display.init(); // Initialize and clear display - display.setTextColor(GxEPD_BLACK); // Set display color - display.setRotation(0); // Set display orientation (horizontal) - display.fillRect(0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, GxEPD_WHITE); // Draw white sreen - display.update(); // Full update (slow) - - if(String(busInfo.displaymode) == "Logo + QR Code" || String(busInfo.displaymode) == "Logo"){ - display.drawExampleBitmap(gImage_Logo_OBP_400x300_sw, 0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, GxEPD_WHITE); // Draw start logo -// display.drawExampleBitmap(gImage_MFD_OBP60_400x300_sw, 0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, GxEPD_WHITE); // Draw start logo - display.updateWindow(0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, true); // Partial update (fast) - delay(SHOW_TIME); // Logo show time - display.fillRect(0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, GxEPD_WHITE); // Draw white sreen - display.updateWindow(0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, true); // Partial update (fast) - if(String(busInfo.displaymode) == "Logo + QR Code"){ - qrWiFi(busInfo); // Show QR code for WiFi connection - delay(SHOW_TIME); // Logo show time - display.fillRect(0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, GxEPD_WHITE); // Draw white sreen - } - } - - // Task Loop - //############################### - while(true){ - // Task cycle time - delay(10); // 10ms - - // Backlight On/Off Subtask 100ms - if((taskRunCounter % 10) == 0){ - // If key controled - if(String(busInfo.backlight) == "Control by Key"){ - if(keystatus == "6s"){ - LOG_DEBUG(GwLog::DEBUG,"Toggle Backlight LED"); - togglePortPin(OBP_BACKLIGHT_LED); - keystatus = "0"; - } - } - // Change page number - if(keystatus == "5s"){ - pageNumber ++; - if(pageNumber > MAX_PAGE_NUMBER - 1){ - pageNumber = 0; - } - first_view = true; - keystatus = "0"; - } - if(keystatus == "1s"){ - pageNumber --; - if(pageNumber < 0){ - pageNumber = MAX_PAGE_NUMBER - 1; - } - first_view = true; - keystatus = "0"; - } - } - - // Subtask all 3000ms - if((taskRunCounter % 300) == 0){ - // LOG_DEBUG(GwLog::DEBUG,"Subtask 2"); - //Clear swipe code - if(keystatus == "left" || keystatus == "right"){ - keystatus = "0"; - } - } - - // Send NMEA0183 GPS data on several bus systems - if(busInfo.gps == true){ // If config enabled - if(gps_ready = true){ - tNMEA0183Msg NMEA0183Msg; - while(NMEA0183.GetMessage(NMEA0183Msg)){ - api->sendNMEA0183Message(NMEA0183Msg); - } - } - } - - //###################################################################### - - // Read the status values from gateway - api->getStatus(status); - busInfo.wifiApOn = status.wifiApOn; - busInfo.wifiClientConnected = status.wifiClientConnected; - busInfo.usbRx = status.usbRx; - busInfo.usbTx = status.usbTx; - busInfo.serRx = status.serRx; - busInfo.serTx = status.serTx; - busInfo.tcpSerRx = status.tcpSerRx; - busInfo.tcpSerTx = status.tcpSerTx; - busInfo.tcpClients = status.tcpClients; - busInfo.tcpClRx = status.tcpClRx; - busInfo.tcpClTx = status.tcpClTx; - busInfo.tcpClientConnected = status.tcpClientConnected; - busInfo.n2kRx = status.n2kRx; - busInfo.n2kTx = status.n2kTx; - - //###################################################################### - - // Read the current bus data and copy to stucture - api->getBoatDataValues(35, valueList); - - busInfo.COG.fvalue = cog->value; - cog->getFormat().toCharArray(busInfo.COG.unit, 8, 0); - busInfo.COG.valid = int(cog->valid); - - busInfo.TWD.fvalue = twd->value; - twd->getFormat().toCharArray(busInfo.TWD.unit, 8, 0); - busInfo.TWD.valid = int(twd->valid); - - busInfo.AWD.fvalue = awd->value; - awd->getFormat().toCharArray(busInfo.AWD.unit, 8, 0); - busInfo.AWD.valid = int(awd->valid); - - busInfo.SOG.fvalue = sog->value; - sog->getFormat().toCharArray(busInfo.SOG.unit, 8, 0); - busInfo.SOG.valid = int(sog->valid); - - busInfo.STW.fvalue = stw->value; - stw->getFormat().toCharArray(busInfo.STW.unit, 8, 0); - busInfo.STW.valid = int(stw->valid); - - busInfo.TWS.fvalue = tws->value; - tws->getFormat().toCharArray(busInfo.TWS.unit, 8, 0); - busInfo.TWS.valid = int(tws->valid); - - busInfo.AWS.fvalue = aws->value; - aws->getFormat().toCharArray(busInfo.AWS.unit, 8, 0); - busInfo.AWS.valid = int(aws->valid); - - busInfo.MaxTws.fvalue = maxtws->value; - maxtws->getFormat().toCharArray(busInfo.MaxTws.unit, 8, 0); - busInfo.MaxTws.valid = int(maxtws->valid); - - busInfo.MaxAws.fvalue = maxaws->value; - maxaws->getFormat().toCharArray(busInfo.MaxAws.unit, 8, 0); - busInfo.MaxAws.valid = int(maxaws->valid); - - busInfo.AWA.fvalue = awa->value; - awa->getFormat().toCharArray(busInfo.AWA.unit, 8, 0); - busInfo.AWA.valid = int(awa->valid); - - busInfo.Heading.fvalue = heading->value; - heading->getFormat().toCharArray(busInfo.Heading.unit, 8, 0); - busInfo.Heading.valid = int(heading->valid); - - busInfo.MagneticHeading.fvalue = mheading->value; - mheading->getFormat().toCharArray(busInfo.MagneticHeading.unit, 8, 0); - busInfo.MagneticHeading.valid = int(mheading->valid); - - busInfo.ROT.fvalue = rot->value; - rot->getFormat().toCharArray(busInfo.ROT.unit, 8, 0); - busInfo.ROT.valid = int(rot->valid); - - busInfo.Variation.fvalue = variation->value; - variation->getFormat().toCharArray(busInfo.Variation.unit, 8, 0); - busInfo.Variation.valid = int(variation->valid); - - busInfo.HDOP.fvalue = hdop->value; - busInfo.HDOP.valid = hdop->valid; - - busInfo.PDOP.fvalue = pdop->value; - busInfo.PDOP.valid = pdop->valid; - - busInfo.VDOP.fvalue = vdop->value; - busInfo.VDOP.valid = vdop->valid; - - busInfo.RudderPosition.fvalue = rudderpos->value; - rudderpos->getFormat().toCharArray(busInfo.RudderPosition.unit, 8, 0); - busInfo.RudderPosition.valid = int(rudderpos->valid); - - formatValue(latitude).toCharArray(busInfo.Latitude.svalue, 16, 0); - busInfo.Latitude.valid = latitude->valid; - - formatValue(longitude).toCharArray(busInfo.Longitude.svalue, 16, 0); - busInfo.Longitude.valid = longitude->valid; - - busInfo.Altitude.fvalue = altitude->value; - altitude->getFormat().toCharArray(busInfo.Altitude.unit, 8, 0); - busInfo.Altitude.valid = int(altitude->valid); - - busInfo.WaterDepth.fvalue = waterdepth->value; - waterdepth->getFormat().toCharArray(busInfo.WaterDepth.unit, 8, 0); - busInfo.WaterDepth.valid = int(waterdepth->valid); - - busInfo.DepthTransducer.fvalue = depthtransducer->value; - depthtransducer->getFormat().toCharArray(busInfo.DepthTransducer.unit, 8, 0); - busInfo.DepthTransducer.valid = int(depthtransducer->valid); - - formatValue(time).toCharArray(busInfo.Time.svalue, 16, 0); - busInfo.Time.valid = time->valid; - - formatValue(date).toCharArray(busInfo.Date.svalue, 16, 0); - busInfo.Date.valid = date->valid; - - busInfo.Timezone.fvalue = timezone->value; - busInfo.Timezone.valid = timezone->valid; - - busInfo.SatInfo.fvalue = satinfo->value; - busInfo.SatInfo.valid = satinfo->valid; - - busInfo.WaterTemperature.fvalue = watertemp->value; - watertemp->getFormat().toCharArray(busInfo.WaterTemperature.unit, 8, 0); - busInfo.WaterTemperature.valid = int(watertemp->valid); - - busInfo.XTE.fvalue = xte->value; - xte->getFormat().toCharArray(busInfo.XTE.unit, 8, 0); - busInfo.XTE.valid = int(xte->valid); - - busInfo.DTW.fvalue = dtw->value; - dtw->getFormat().toCharArray(busInfo.DTW.unit, 8, 0); - busInfo.DTW.valid = int(dtw->valid); - - busInfo.BTW.fvalue = btw->value; - btw->getFormat().toCharArray(busInfo.BTW.unit, 8, 0); - busInfo.BTW.valid = int(btw->valid); - - formatValue(wplatitude).toCharArray(busInfo.WPLatitude.svalue, 16, 0); - busInfo.WPLatitude.valid = wplatitude->valid; - - formatValue(wplongitude).toCharArray(busInfo.WPLongitude.svalue, 16, 0); - busInfo.WPLongitude.valid = wplongitude->valid; - - busInfo.Log.fvalue = log->value; - log->getFormat().toCharArray(busInfo.Log.unit, 8, 0); - busInfo.Log.valid = int(log->valid); - - busInfo.TripLog.fvalue = triplog->value; - triplog->getFormat().toCharArray(busInfo.TripLog.unit, 8, 0); - busInfo.TripLog.valid = int(triplog->valid); - - //###################################################################### - - - // Subtask all 500ms show pages - if((taskRunCounter % 50) == 0 || first_view == true){ - LOG_DEBUG(GwLog::DEBUG,"Keystatus: %s", keystatus); - LOG_DEBUG(GwLog::DEBUG,"Pagenumber: %d", pageNumber); - if(String(busInfo.displaymode) == "Logo + QR Code" || String(busInfo.displaymode) == "Logo" || String(busInfo.displaymode) == "White Screen"){ - showPage(busInfo); - } - } - - // Subtask E-Ink full refresh - if((taskRunCounter % (FULL_REFRESH_TIME * 100)) == 0){ - LOG_DEBUG(GwLog::DEBUG,"E-Ink full refresh"); - display.update(); - } - - taskRunCounter++; - } - vTaskDelete(NULL); - -} - -#endif diff --git a/lib/obp60task/GwOBP60Task.h b/lib/obp60task/GwOBP60Task.h deleted file mode 100644 index b6e8b3b..0000000 --- a/lib/obp60task/GwOBP60Task.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef _GWOBP60TASK_H -#define _GWOBP60TASK_H -#include "GwApi.h" -//we only compile for some boards -#ifdef BOARD_NODEMCU32S_OBP60 - // CAN NMEA2000 - #define ESP32_CAN_TX_PIN GPIO_NUM_13 - #define ESP32_CAN_RX_PIN GPIO_NUM_12 - // Bus load in 50mA steps - #define N2K_LOAD_LEVEL 5 // 5x50mA = 250mA max bus load with back light on - // RS485 NMEA0183 - #define GWSERIAL_TX 26 - #define GWSERIAL_RX 14 - #define GWSERIAL_MODE "UNI" - - // Init OBP60 Task - void OBP60Init(GwApi *param); - DECLARE_INITFUNCTION(OBP60Init); - - // OBP60 Task - void OBP60Task(void *param); - DECLARE_USERTASK_PARAM(OBP60Task, 25000) // Need 25k RAM as stack size - DECLARE_CAPABILITY(obp60,true); -#endif -#endif diff --git a/lib/obp60task/OBP60Data.h b/lib/obp60task/OBP60Data.h deleted file mode 100644 index 62187c4..0000000 --- a/lib/obp60task/OBP60Data.h +++ /dev/null @@ -1,122 +0,0 @@ -#ifndef _OBP60Data_H -#define _OBP60Data_H - -#include - -float convert_m2ft(float inputvalue){ - return inputvalue * 3.28084; -} - -typedef struct{ // Sub structure for bus data - float fvalue = 0; // Float value - char svalue[16] = ""; // Char value - char unit[8] = ""; // Unit - bool valid = 0; // Valid flag -} dataContainer; - -typedef struct{ - // Gateway status infos - bool wifiApOn = false; // Status access point [on|off] - bool wifiClientConnected = false; // Client connected [yes|no] - unsigned long usbRx = 0; // USB receive traffic - unsigned long usbTx = 0; // USB send traffic - unsigned long serRx = 0; // MNEA0183 serial receive traffic - unsigned long serTx = 0; // NMEA0183 serial send traffic - unsigned long tcpSerRx = 0; // MNEA0183 TCP server receive traffic - unsigned long tcpSerTx = 0; // MNEA0183 TCP server send traffic - int tcpClients = 0; // Number of connected TCP clients - unsigned long tcpClRx = 0; // MNEA0183 TCP client receive traffic - unsigned long tcpClTx = 0; // MNEA0183 TCP client send traffic - bool tcpClientConnected = false; // Connected TCP clients - unsigned long n2kRx = 0; // NMEA2000 CAN receive traffic - unsigned long n2kTx = 0; // NMEA2000 CAN send traffic - // System Settings - char systemname[32] = ""; // System name show on web page and mDNS name - char wifissid[32] = ""; // WiFi access point SSID - char wifipass[32] = ""; // WiFi access point password - bool useadminpass = false; // Use admin password [on|off] - char adminpassword[32] = ""; // Admin password - char loglevel[16] = ""; // Loglevel [off|error|log|debug] - // WiFi client settings - bool wificlienton = false; // Is WiFi client on [on|off] - char wificlientssid[32] = ""; // Wifi client SSID - char wificlientpass[32] = ""; // Wifi client password - // OBP60 Settings - char lengthformat[16] = ""; // Length format [m|ft] - char distanceformat[16] = ""; // Distance format [m|km|nm] - char speedformat[16] = ""; // Speed format [m/s|km/h|kn] - char windspeedformat[16] = ""; // Speed format [m/s|km/h|kn|bft] - char tempformat[16] = ""; // Temperature format [K|C|F] - char dateformat[3] = ""; // Date format for status line [DE|GB|US] - int timezone = 0; // Time zone [-12...+12] - float draft = 0; // Boat draft up to keel [m] - float fueltank = 0; // Fuel tank capacity [0...10m] - float fuelconsumption = 0; // Fuel consumption [0...1000l/min] - float watertank = 0; // Water tank kapacity [0...5000l] - float wastetank = 0; // Waste tank kapacity [0...5000l] - float batvoltage = 0; // Battery voltage [0...1000V] - char battype[16] = ""; // Battery type [Pb|Gel|AGM|LiFePo4] - float batcapacity = 0; // Battery capacity [0...10000Ah] - // OBP60 Hardware - bool gps = false; // Internal GPS [on|off] - bool bme280 = false; // Internat BME280 [on|off] - bool onewire = false; // Internal 1Wire bus [on|off] - char powermode[16] = ""; // Power mode [Max Power|Only 3.3V|Only 5.0V|Min Power] - bool simulation = false; // Simulation data [on|off] - // OBP60 Display - char displaymode[16] = ""; // Dislpay mode [White Screen|Logo|Logo + QR Code|Off] - bool statusline = true; // Show status line [on|off] - bool refresh = false; // Refresh display after select a new page [on|off] - bool holdvalues = false; // Hold values on missing data stream [on|off] - char backlight[16] = ""; // Backlight mode [Off|Control by Sun|Control by Bus|Control by Time|Control by Key|On] - char flashled[16] = ""; // Flash LED mode [Off|Bus Data|GPX Fix|Limits Overrun] - // OBP60 Buzzer - bool buzerror = false; // Buzzer error [on|off] - bool buzgps = false; // Buzzer by GPS error [on|off] - bool buzlimits = false; // Buzzer by limit underruns and overruns [on|off] - char buzmode[16] = ""; // Buzzer mode [Off|Short Single Beep|Lond Single Beep|Beep until Confirmation] - int buzpower = 0; // Buzzer power [0...100%] - // OBP60 Pages - int numpages = 1; // Numper of listed pages - // Bus data - dataContainer AWA; - dataContainer AWD; - dataContainer AWS; - dataContainer Altitude; - dataContainer BTW; - dataContainer COG; - dataContainer DTW; - dataContainer Date; - dataContainer DepthTransducer; - dataContainer Deviation; - dataContainer HDOP; - dataContainer Heading; - dataContainer Latitude; - dataContainer Log; - dataContainer Longitude; - dataContainer MagneticHeading; - dataContainer MaxAws; - dataContainer MaxTws; - dataContainer PDOP; - dataContainer ROT; - dataContainer RudderPosition; - dataContainer SOG; - dataContainer STW; - dataContainer SatInfo; - dataContainer Time; - dataContainer TWD; - dataContainer TWS; - dataContainer Timezone; - dataContainer TripLog; - dataContainer VDOP; - dataContainer Variation; - dataContainer WPLatitude; - dataContainer WPLongitude; - dataContainer WaterDepth; - dataContainer WaterTemperature; - dataContainer XTE; -} busData; - -busData busInfo; - -#endif \ No newline at end of file diff --git a/lib/obp60task/OBP60ExtensionPort.h b/lib/obp60task/OBP60ExtensionPort.h deleted file mode 100644 index dff8776..0000000 --- a/lib/obp60task/OBP60ExtensionPort.h +++ /dev/null @@ -1,106 +0,0 @@ -#ifndef _OBP60EXTENSIONPORT_H -#define _OBP60EXTENSIONPORT_H - -#include -#include "OBP60Hardware.h" - -MCP23017 mcp = MCP23017(MCP23017_I2C_ADDR); - -// SPI pin definitions for E-Ink display -GxIO_Class io(SPI, OBP_SPI_CS, OBP_SPI_DC, OBP_SPI_RST); // SPI, CS, DC, RST -GxEPD_Class display(io, OBP_SPI_RST, OBP_SPI_BUSY); // io, RST, BUSY - -// Global vars -int outA = 0; // Outport Byte A -int outB = 0; // Outport Byte B -bool blinkingLED = false; // Enable / disable blinking flash LED -int uvDuration = 0; // Under voltage duration in n x 100ms -uint buzPower = 50; // Buzzer loudness in [%] - -void setPortPin(uint pin, bool value){ - - if(pin <=7){ - - outA &= ~(1 << pin); // Clear bit - - outA |= (value << pin); // Set bit - mcp.writeRegister(MCP23017Register::GPIO_A, outA); - } - else{ - pin = pin - 8; - outB &= ~(1 << pin); // Clear bit - outB |= (value << pin); // Set bit - mcp.writeRegister(MCP23017Register::GPIO_B, outB); - } -} - -void togglePortPin(uint pin){ - if(pin <=7){ - outA ^= (1 << pin); // Set bit - mcp.writeRegister(MCP23017Register::GPIO_A, outA); - } - else{ - pin = pin - 8; - outB ^= (1 << pin); // Set bit - mcp.writeRegister(MCP23017Register::GPIO_B, outB); - } -} - -void blinkingFlashLED(){ - noInterrupts(); - if(blinkingLED == true){ - togglePortPin(OBP_FLASH_LED); - } - else{ - setPortPin(OBP_FLASH_LED, false); - } - interrupts(); -} - -void buzzer(uint frequency, uint power, uint duration){ - if(frequency > 8000){ // Max 8000Hz - frequency = 8000; - } - if(power > 100){ // Max 100% - power = 100; - } - if(duration > 1000){ // Max 1000ms - duration = 1000; - } - - pinMode(OBP_BUZZER, OUTPUT); - ledcSetup(0, frequency, 8); // Ch 0, ferquency in Hz, 8 Bit resolution of PWM - ledcAttachPin(OBP_BUZZER, 0); - ledcWrite(0, int(power * 1.28)); // 50% duty cycle are 100% - delay(duration); - ledcWrite(0, 0); // 0% duty cycle are 0% -} - -void underVoltageDetection(){ - noInterrupts(); - float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 - if(actVoltage < MIN_VOLTAGE){ - uvDuration ++; - } - else{ - uvDuration = 0; - } - if(uvDuration > POWER_FAIL_TIME){ - setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off - setPortPin(OBP_FLASH_LED, false); // Flash LED Off - buzzer(TONE4, buzPower, 20); // Buzzer tone 4kHz 20% 20ms - setPortPin(OBP_POWER_50, false); // Power rail 5.0V Off - setPortPin(OBP_POWER_33, false); // Power rail 3.3V Off - // Shutdown EInk display - display.fillRect(0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, GxEPD_WHITE); // Draw white sreen - display.updateWindow(0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, false); // Partial update - // display._sleep(); // Display shut dow - // Stop system - while(true){ - esp_deep_sleep_start(); // Deep Sleep without weakup. Weakup only after power cycle (restart). - } - } - interrupts(); -} - -#endif \ No newline at end of file diff --git a/lib/obp60task/OBP60Keypad.h b/lib/obp60task/OBP60Keypad.h deleted file mode 100644 index fdbc6be..0000000 --- a/lib/obp60task/OBP60Keypad.h +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef _OBP60FUNCTIONS_H -#define _OBP60FUNCTIONS_H - -#include -#include "OBP60Hardware.h" - -// Global vars - -// Routine for TTP229-BSF (!!! IC use not a I2C bus !!!) -// -// 8 Key Mode -// -// Key number {0, 1, 2, 3, 4, 5, 6, 7, 8} -// Scan code {0, 8, 7, 1, 6, 2, 5, 3, 4} -int keyposition[9] = {0, 3, 5, 7, 8, 6, 4, 2, 1}; // Position of key in raw data, 0 = nothing touched -int keypad[9]; // Raw data array from TTP229 -int key; // Value of key [0|1], 0 = touched, 1 = not touched -int keycode = 0; // Keycode of pressed key [0...8], 0 = nothing touched -String keystatus = "0"; // Status of key (0 = processed, 1s...8s, 1l...8l, left, right unprocessed) -int keycodeold; -int swipedelay = 500; // Delay after swipe in [ms] -int keydelay = 500; // Delay after key pressed in [ms] -bool keylock = 0; // Key lock after pressed key is valid (repeat protection by conginous pressing) -int repeatnum; // Actual number of key detections for a pressed key -int shortpress = 5; // number of key detections after that the key is valid (n * 40ms) for short pessing -int longpress = 25; // number of key detections after that the key is valid (n * 40ms) for long pessing -int swipedir = 0; // Swipe direction 0 = nothing, -1 = left, +1 = right - - -void readKeypad() { - noInterrupts(); - pinMode(TTP_SDO, INPUT); - pinMode(TTP_SCL, OUTPUT); - keycode = 0; - // Read key code from raw data - for (int i = 0; i < 9; i++) { - digitalWrite(TTP_SCL, LOW); - delay(1); // 2ms clock - keypad[i] = digitalRead(TTP_SDO); - if(i > 0){ - // Invert keypad - if(keypad[i] == 1){ - key = 0; - } - else{ - key = 1; - } - keycode += key * i; - } - digitalWrite(TTP_SCL, HIGH); - delay(1); - } - // Remapping keycode - keycode = keyposition[keycode]; - - // Read repeat number - if(keycode == keycodeold){ - repeatnum ++; - } -/* - // Detect swipe right - if (keycode > 0 && keycodeold > 0 && keycode > keycodeold && keycode != keycodeold && keylock == false){ - keylock = true; - swipedir = 1; - keystatus = "right"; - buzzer(TONE3 buzPower, 100); - delay(swipedelay); - } - // Detect swipe left - if (keycode > 0 && keycodeold > 0 && keycode < keycodeold && keycode != keycodeold && keylock == false){ - keylock = true; - swipedir = -1; - keystatus = "left"; - buzzer(TONE3, buzPower, 100); - delay(swipedelay); - } -*/ - // Detect short perssed keynumber - if (keycode > 0 && repeatnum >= shortpress && repeatnum < longpress && keycode == keycodeold && keylock == false){ - keylock = true; - keystatus = String(keycode) + "s"; - buzzer(TONE4, buzPower, 100); - delay(keydelay); - } - /* - // Detect long perssed keynumber - if (keycode > 0 && repeatnum >= longpress && keylock == false){ - keystatus = String(keycode) + "l"; - buzzer(4000, 20, 200); - keylock = true; - } - */ - // Reset keylock after release - if (keycode == 0 && keycodeold == 0){ - keylock = false; - repeatnum = 0; - } - // Copy keycode - keycodeold = keycode; - interrupts(); -} - -#endif \ No newline at end of file diff --git a/lib/obp60task/OBP60Pages.h b/lib/obp60task/OBP60Pages.h deleted file mode 100644 index dea0c44..0000000 --- a/lib/obp60task/OBP60Pages.h +++ /dev/null @@ -1,172 +0,0 @@ -#ifndef _OBP60PAGES_H -#define _OBP60PAGES_H - -#include - -// Global vars -int pageNumber = 0; // Page number for actual page -bool first_view = true; -bool heartbeat = false; -unsigned long usbRxOld = 0; -unsigned long usbTxOld = 0; -unsigned long serRxOld = 0; -unsigned long serTxOld = 0; -unsigned long tcpSerRxOld = 0; -unsigned long tcpSerTxOld = 0; -unsigned long tcpClRxOld = 0; -unsigned long tcpClTxOld = 0; -unsigned long n2kRxOld = 0; -unsigned long n2kTxOld = 0; - -void showPage(busData values){ - // Clear display - display.fillRect(0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, GxEPD_WHITE); // Draw white sreen - - if(values.statusline == true){ - // Print status info - display.setFont(&Ubuntu_Bold8pt7b); - display.setTextColor(GxEPD_BLACK); - display.setCursor(0, 15); - if(values.wifiApOn){ - display.print(" AP "); - } - // If receive new telegram data then display bus name - if(values.tcpClRx != tcpClRxOld || values.tcpClTx != tcpClTxOld || values.tcpSerRx != tcpSerRxOld || values.tcpSerTx != tcpSerTxOld){ - display.print("TCP "); - } - if(values.n2kRx != n2kRxOld || values.n2kTx != n2kTxOld){ - display.print("N2K "); - } - if(values.serRx != serRxOld || values.serTx != serTxOld){ - display.print("183 "); - } - if(values.usbRx != usbRxOld || values.usbTx != usbTxOld){ - display.print("USB "); - } - if(values.gps == true && values.PDOP.valid == true && values.PDOP.fvalue <= 50){ - display.print("GPS"); - } - // Save old telegram counter - tcpClRxOld = values.tcpClRx; - tcpClTxOld = values.tcpClTx; - tcpSerRxOld = values.tcpSerRx; - tcpSerTxOld = values.tcpSerTx; - n2kRxOld = values.n2kRx; - n2kTxOld = values.n2kTx; - serRxOld = values.serRx; - serTxOld = values.serTx; - usbRxOld = values.usbRx; - usbTxOld = values.usbTx; - - // Heartbeat as dot - display.setFont(&Ubuntu_Bold32pt7b); - display.setCursor(205, 14); - if(heartbeat == true){ - display.print("."); - } - else{ - display.print(" "); - } - heartbeat = !heartbeat; - - // Date and time - display.setFont(&Ubuntu_Bold8pt7b); - display.setCursor(230, 15); - if(values.HDOP.valid == true && values.HDOP.fvalue <= 50){ - char newdate[16] = ""; - if(String(values.dateformat) == "DE"){ - display.print(values.Date.svalue); - } - if(String(values.dateformat) == "GB"){ - values.Date.svalue[2] = '/'; - values.Date.svalue[5] = '/'; - display.print(values.Date.svalue); - } - if(String(values.dateformat) == "US"){ - char newdate[16] = ""; - strcpy(newdate, values.Date.svalue); - newdate[0] = values.Date.svalue[3]; - newdate[1] = values.Date.svalue[4]; - newdate[2] = '/'; - newdate[3] = values.Date.svalue[0]; - newdate[4] = values.Date.svalue[1]; - newdate[5] = '/'; - display.print(newdate); - } - display.print(" "); - if(values.timezone == 0){ - display.print(values.Time.svalue); - display.print(" "); - display.print("UTC"); - } - else{ - char newtime[16] = ""; - char newhour[3] = ""; - int hour = 0; - strcpy(newtime, values.Time.svalue); - newhour[0] = values.Time.svalue[0]; - newhour[1] = values.Time.svalue[1]; - hour = strtol(newhour, 0, 10); - if(values.timezone > 0){ - hour += values.timezone; - } - else{ - hour += values.timezone + 24; - } - hour %= 24; - sprintf(newhour, "%02d", hour); - newtime[0] = newhour[0]; - newtime[1] = newhour[1]; - display.print(newtime); - display.print(" "); - display.print("LOT"); - } - } - else{ - display.print("No GPS data"); - } - } - - // Read page number - switch (pageNumber) { - case 0: - page_0(values); - break; - case 1: - page_1(); - break; - case 2: - page_2(); - break; - case 3: - page_3(); - break; - case 4: - // Statement(s) - break; - case 5: - // Statement(s) - break; - default: - page_0(values); - break; - } - - // Update display - if(values.refresh == true){ - if(first_view == true){ - display.updateWindow(0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, true); // Needs partial update before full update to refresh the frame buffer - display.update(); // Full update - } - else{ - display.updateWindow(0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, true); // Partial update (fast) - } - } - else{ - display.updateWindow(0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, true); // Partial update (fast) - } - - first_view = false; -} - -#endif \ No newline at end of file diff --git a/lib/obp60task/OBP60QRWiFi.h b/lib/obp60task/OBP60QRWiFi.h deleted file mode 100644 index dd820b3..0000000 --- a/lib/obp60task/OBP60QRWiFi.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef _OBP60QRWIFI_H -#define _OBP60QRWIFI_H - -#include -#include "qrcode.h" - -void qrWiFi(busData values){ - // Set start point and pixel size - int16_t box_x = 100; // X offset - int16_t box_y = 30; // Y offset - int16_t box_s = 6; // Pixel size - int16_t init_x = box_x; - - // Create the QR code - QRCode qrcode; - uint8_t qrcodeData[qrcode_getBufferSize(4)]; - // Content for QR code: "WIFI:S:mySSID;T:WPA;P:myPASSWORD;;" - String text = "WIFI:S:" + String(values.systemname) + ";T:WPA;P:" + String(values.wifipass) + ";;"; - const char *qrcodecontent = text.c_str(); - qrcode_initText(&qrcode, qrcodeData, 4, 0, qrcodecontent); - - // Top quiet zone - for (uint8_t y = 0; y < qrcode.size; y++) { - // Each horizontal module - for (uint8_t x = 0; x < qrcode.size; x++) { - if(qrcode_getModule(&qrcode, x, y)){ - display.fillRect(box_x, box_y, box_s, box_s, GxEPD_BLACK); - } else { - display.fillRect(box_x, box_y, box_s, box_s, GxEPD_WHITE); - } - box_x = box_x + box_s; - } - box_y = box_y + box_s; - box_x = init_x; - } - display.setFont(&Ubuntu_Bold32pt7b); - display.setTextColor(GxEPD_BLACK); - display.setCursor(140, 285); - display.print("WiFi"); - if(values.refresh == true){ - display.update(); // Full update (slow) - } - else{ - display.updateWindow(0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, true); // Partial update (fast) - } -} - -#endif \ No newline at end of file diff --git a/lib/obp60task/PageApparentWind.cpp b/lib/obp60task/PageApparentWind.cpp new file mode 100644 index 0000000..5929d18 --- /dev/null +++ b/lib/obp60task/PageApparentWind.cpp @@ -0,0 +1,46 @@ +#include "Pagedata.h" + +class PageApparentWind : public Page +{ + int dummy=0; //an example on how you would maintain some status + //for a page +public: + PageApparentWind(CommonData &common){ + common.logger->logDebug(GwLog::LOG,"created PageApparentWind"); + dummy=1; + } + virtual void display(CommonData &commonData, PageData &pageData) + { + GwLog *logger = commonData.logger; + dummy++; + for (int i = 0; i < 2; i++) + { + GwApi::BoatValue *value = pageData.values[i]; + if (value == NULL) + continue; + LOG_DEBUG(GwLog::LOG, "drawing at PageApparentWind(%d),dummy=%d, p=%s,v=%f", + i, + dummy, + value->getName().c_str(), + value->valid ? value->value : -1.0); + } + }; +}; + +static Page *createPage(CommonData &common){ + return new PageApparentWind(common); +} +/** + * with the code below we make this page known to the PageTask + * we give it a type (name) that can be selected in the config + * we define which function is to be called + * and we provide the number of user parameters we expect (0 here) + * and will will provide the names of the fixed values we need + */ +PageDescription registerPageApparentWind( + "apparentWind", + createPage, + 0, + {"AWS","AWD"}, + false +); \ No newline at end of file diff --git a/lib/obp60task/PageForValues.cpp b/lib/obp60task/PageForValues.cpp new file mode 100644 index 0000000..c815287 --- /dev/null +++ b/lib/obp60task/PageForValues.cpp @@ -0,0 +1,36 @@ +#include "Pagedata.h" + +class PageForValues : public Page +{ +public: + virtual void display(CommonData &commonData, PageData &pageData) + { + GwLog *logger = commonData.logger; + for (int i = 0; i < 4; i++) + { + GwApi::BoatValue *value = pageData.values[i]; + if (value == NULL) + continue; + LOG_DEBUG(GwLog::LOG, "drawing at PageforValues(%d), p=%s,v=%f", + i, + value->getName().c_str(), + value->valid ? value->value : -1.0); + } + }; +}; + +static Page *createPage(CommonData &){ + return new PageForValues(); +} +/** + * with the code below we make this page known to the PageTask + * we give it a type (name) that can be selected in the config + * we define which function is to be called + * and we provide the number of user parameters we expect + * this will be number of BoatValue pointers in pageData.values + */ +PageDescription registerPageForValues( + "forValues", + createPage, + 4 +); \ No newline at end of file diff --git a/lib/obp60task/PageOneValue.cpp b/lib/obp60task/PageOneValue.cpp new file mode 100644 index 0000000..a0e1517 --- /dev/null +++ b/lib/obp60task/PageOneValue.cpp @@ -0,0 +1,29 @@ +#include "Pagedata.h" + +class PageOneValue : public Page{ + public: + virtual void display(CommonData &commonData, PageData &pageData){ + GwLog *logger=commonData.logger; + GwApi::BoatValue *value=pageData.values[0]; + if (value == NULL) return; + LOG_DEBUG(GwLog::LOG,"drawing at PageOneValue, p=%s,v=%f", + value->getName().c_str(), + value->valid?value->value:-1.0 + ); + }; +}; + +static Page* createPage(CommonData &common){return new PageOneValue();} + +/** + * with the code below we make this page known to the PageTask + * we give it a type (name) that can be selected in the config + * we define which function is to be called + * and we provide the number of user parameters we expect + * this will be number of BoatValue pointers in pageData.values + */ +PageDescription registerPageOneValue( + "oneValue", + createPage, + 1 +); \ No newline at end of file diff --git a/lib/obp60task/PageThreeValues.cpp b/lib/obp60task/PageThreeValues.cpp new file mode 100644 index 0000000..742af48 --- /dev/null +++ b/lib/obp60task/PageThreeValues.cpp @@ -0,0 +1,36 @@ +#include "Pagedata.h" + +class PageThreeValues : public Page +{ +public: + virtual void display(CommonData &commonData, PageData &pageData) + { + GwLog *logger = commonData.logger; + for (int i = 0; i < 3; i++) + { + GwApi::BoatValue *value = pageData.values[i]; + if (value == NULL) + continue; + LOG_DEBUG(GwLog::LOG, "drawing at PageThreeValues(%d), p=%s,v=%f", + i, + value->getName().c_str(), + value->valid ? value->value : -1.0); + } + }; +}; + +static Page *createPage(CommonData &){ + return new PageThreeValues(); +} +/** + * with the code below we make this page known to the PageTask + * we give it a type (name) that can be selected in the config + * we define which function is to be called + * and we provide the number of user parameters we expect + * this will be number of BoatValue pointers in pageData.values + */ +PageDescription registerPageThreeValuese( + "threeValues", + createPage, + 3 +); \ No newline at end of file diff --git a/lib/obp60task/PageTwoValues.cpp b/lib/obp60task/PageTwoValues.cpp new file mode 100644 index 0000000..1b69608 --- /dev/null +++ b/lib/obp60task/PageTwoValues.cpp @@ -0,0 +1,40 @@ +#include "Pagedata.h" + +class PageTwoValues : public Page +{ +public: + PageTwoValues(CommonData &comon){ + comon.logger->logDebug(GwLog::LOG,"created PageTwoValue"); + //add some initialization code here + } + virtual void display(CommonData &commonData, PageData &pageData) + { + GwLog *logger = commonData.logger; + for (int i = 0; i < 2; i++) + { + GwApi::BoatValue *value = pageData.values[i]; + if (value == NULL) + continue; + LOG_DEBUG(GwLog::LOG, "drawing at PageTwoValues(%d), p=%s,v=%f", + i, + value->getName().c_str(), + value->valid ? value->value : -1.0); + } + }; +}; + +static Page *createPage(CommonData &common){ + return new PageTwoValues(common); +} +/** + * with the code below we make this page known to the PageTask + * we give it a type (name) that can be selected in the config + * we define which function is to be called + * and we provide the number of user parameters we expect + * this will be number of BoatValue pointers in pageData.values + */ +PageDescription registerPageTwoValues( + "twoValues", + createPage, + 2 +); \ No newline at end of file diff --git a/lib/obp60task/Page_0.h b/lib/obp60task/Page_0.h deleted file mode 100644 index 0e2b472..0000000 --- a/lib/obp60task/Page_0.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef _PAGE_0_H -#define _PAGE_0_H - -#include -#include "OBP60Hardware.h" -// Hallo -void page_0(busData pvalues){ - // Show name - display.setFont(&Ubuntu_Bold32pt7b); - display.setTextColor(GxEPD_BLACK); - display.setCursor(20, 100); - display.print("Depth"); - display.setFont(&Ubuntu_Bold20pt7b); - display.setCursor(270, 100); - // Show unit - if(String(pvalues.lengthformat) == "m"){ - display.print("m"); - } - if(String(pvalues.lengthformat) == "ft"){ - display.print("ft"); - } - display.setFont(&DSEG7Classic_BoldItalic60pt7b); - display.setCursor(20, 240); - - // Reading bus data or using simulation data - float depth = 0; - if(pvalues.simulation == true){ - depth = 84; - depth += float(random(0, 120)) / 10; // Simulation data - display.print(depth,1); - } - else{ - // Check vor valid real data, display also if hold values activated - if(pvalues.WaterDepth.valid == true || pvalues.holdvalues == true){ - // Unit conversion - if(String(pvalues.lengthformat) == "m"){ - depth = pvalues.WaterDepth.fvalue; // Real bus data m - } - if(String(pvalues.lengthformat) == "ft"){ - depth = convert_m2ft(pvalues.WaterDepth.fvalue); // Bus data in ft - } - // Resolution switching - if(depth <= 99.9){ - display.print(depth,1); - } - else{ - display.print(depth,0); - } - } - else{ - display.print("---"); // Missing bus data - } - } - - // Key Layout - display.setFont(&Ubuntu_Bold8pt7b); - display.setTextColor(GxEPD_BLACK); - display.setCursor(0, 290); - display.print(" [ < ]"); - display.setCursor(290, 290); - display.print("[ > ]"); - display.setCursor(343, 290); - display.print("[ILUM]"); -} - -#endif \ No newline at end of file diff --git a/lib/obp60task/Page_1.h b/lib/obp60task/Page_1.h deleted file mode 100644 index 1f3deca..0000000 --- a/lib/obp60task/Page_1.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef _PAGE_1_H -#define _PAGE_1_H - -#include -#include "OBP60Hardware.h" - -void page_1(){ - // Measuring Values - display.setFont(&Ubuntu_Bold32pt7b); - display.setTextColor(GxEPD_BLACK); - display.setCursor(20, 100); - display.print("Speed"); - display.setFont(&Ubuntu_Bold20pt7b); - display.setCursor(270, 100); - display.print("km/h"); - display.setFont(&DSEG7Classic_BoldItalic60pt7b); - display.setCursor(20, 240); - float speed = 5; - speed += float(random(0, 13)) / 10; - display.print(speed,1); - - // Key Layout - display.setFont(&Ubuntu_Bold8pt7b); - display.setTextColor(GxEPD_BLACK); - display.setCursor(0, 290); - display.print(" [ < ]"); - display.setCursor(290, 290); - display.print("[ > ]"); - display.setCursor(343, 290); - display.print("[ILUM]"); -} - -#endif \ No newline at end of file diff --git a/lib/obp60task/Page_2.h b/lib/obp60task/Page_2.h deleted file mode 100644 index 323d637..0000000 --- a/lib/obp60task/Page_2.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef _PAGE_2_H -#define _PAGE_2_H - -#include -#include "OBP60Hardware.h" - -void page_2(){ - // Measuring Values - display.setFont(&Ubuntu_Bold32pt7b); - display.setTextColor(GxEPD_BLACK); - display.setCursor(20, 100); - display.print("VBat"); - display.setFont(&Ubuntu_Bold20pt7b); - display.setCursor(270, 100); - display.print("V"); - display.setFont(&DSEG7Classic_BoldItalic60pt7b); - display.setCursor(20, 240); - float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 - display.print(actVoltage,1); - - // Key Layout - display.setFont(&Ubuntu_Bold8pt7b); - display.setTextColor(GxEPD_BLACK); - display.setCursor(0, 290); - display.print(" [ < ]"); - display.setCursor(290, 290); - display.print("[ > ]"); - display.setCursor(343, 290); - display.print("[ILUM]"); -} - -#endif \ No newline at end of file diff --git a/lib/obp60task/Page_3.h b/lib/obp60task/Page_3.h deleted file mode 100644 index ce7475a..0000000 --- a/lib/obp60task/Page_3.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef _PAGE_3_H -#define _PAGE_3_H - -#include -#include "OBP60Hardware.h" - -void page_3(){ - // Measuring Values 1 - display.setFont(&Ubuntu_Bold20pt7b); - display.setTextColor(GxEPD_BLACK); - display.setCursor(20, 80); - display.print("Depth"); - display.setFont(&Ubuntu_Bold20pt7b); - display.setCursor(20, 130); - display.print("m"); - display.setFont(&DSEG7Classic_BoldItalic42pt7b); - display.setCursor(180, 130); - float depth = 84; - depth += float(random(0, 13)) / 10; - display.print(depth,1); - - // Horizontal line 3 pix - display.fillRect(0, 145, 400, 3, GxEPD_BLACK); // Draw white sreen - - // Measuring Values 2 - display.setFont(&Ubuntu_Bold20pt7b); - display.setTextColor(GxEPD_BLACK); - display.setCursor(20, 190); - display.print("Speed"); - display.setFont(&Ubuntu_Bold20pt7b); - display.setCursor(20, 240); - display.print("kn"); - display.setFont(&DSEG7Classic_BoldItalic42pt7b); - display.setCursor(180, 240); - float speed = 5; - speed += float(random(0, 13)) / 10; - display.print(speed,1); - - - // Key Layout - display.setFont(&Ubuntu_Bold8pt7b); - display.setTextColor(GxEPD_BLACK); - display.setCursor(0, 290); - display.print(" [ < ]"); - display.setCursor(290, 290); - display.print("[ > ]"); - display.setCursor(343, 290); - display.print("[ILUM]"); -} - -#endif \ No newline at end of file diff --git a/lib/obp60task/Pagedata.h b/lib/obp60task/Pagedata.h new file mode 100644 index 0000000..1b947e6 --- /dev/null +++ b/lib/obp60task/Pagedata.h @@ -0,0 +1,68 @@ +#pragma once +#include +#include "GwApi.h" +#include +#include + +#define MAX_PAGE_NUMBER 4 +typedef std::vector ValueList; +typedef struct{ + String pageName; + //the values will always contain the user defined values first + ValueList values; +} PageData; + +typedef struct{ + +}OutputData; + +typedef struct{ + String distanceformat="m"; + String lengthformat="m"; + //... + OutputData output; + GwApi::Status status; + GwLog *logger=NULL; +} CommonData; + +//a base class that all pages must inherit from +class Page{ + public: + virtual void display(CommonData &commonData, PageData &pageData)=0; + virtual void displayNew(CommonData &commonData, PageData &pageData){} + //return -1 if handled by the page + virtual int handleKey(int key){return key;} +}; + +typedef std::function PageFunction; +typedef std::vector StringList; + +/** + * a class that describes a page + * it contains the name (type) + * the number of expected user defined boat Values + * and a list of boatValue names that are fixed + * for each page you define a variable of this type + * and add this to registerAllPages in the obp60task + */ +class PageDescription{ + public: + String pageName; + int userParam=0; + StringList fixedParam; + PageFunction creator; + bool header=true; + PageDescription(String name, PageFunction creator,int userParam,StringList fixedParam,bool header=true){ + this->pageName=name; + this->userParam=userParam; + this->fixedParam=fixedParam; + this->creator=creator; + this->header=header; + } + PageDescription(String name, PageFunction creator,int userParam,bool header=true){ + this->pageName=name; + this->userParam=userParam; + this->creator=creator; + this->header=header; + } +}; diff --git a/lib/obp60task/Readme.md b/lib/obp60task/Readme.md deleted file mode 100644 index d9c3abf..0000000 --- a/lib/obp60task/Readme.md +++ /dev/null @@ -1,51 +0,0 @@ -Extending the Core -================== -This directory contains an example on how you can extend the base functionality of the gateway. -Maybe you have another interesting hardware or need some additional functions but would like to use the base functionality of the gateway. -You can define own hardware configurations (environments) here and can add one or more tasks that will be started by the core. -You can also add additional libraries that will be used to build your task. -In this example we define an addtional board (environment) with the name "testboard". -When building for this board we add the -DTEST_BOARD to the compilation - see [platformio.ini](platformio.ini). -The additional task that we defined will only be compiled and started for this environment (see the #ifdef TEST_BOARD in the code). -You can add your own directory below "lib". The name of the directory must contain "task". - -Files ------ - * [platformio.ini](platformio.ini)
- This file is completely optional. - You only need this if you want to - extend the base configuration - we add a dummy library here and define one additional build environment (board) - * [GwExampleTask.h](GwExampleTask.h) the name of this include must match the name of the directory (ignoring case) with a "gw" in front. This file includes our special hardware definitions and registers our task at the core (DECLARE_USERTASK in the code). Optionally it can define some capabilities (using DECLARE_CAPABILITY) that can be used in the config UI (see below). - Avoid including headers from other libraries in this file as this could interfere with the main code. Just only include them in your .cpp files (or in other headers). - * [GwExampleTaks.cpp](GwExampleTask.cpp) includes the implementation of our task. This tasks runs in an own thread - see the comments in the code. - We can have as many cpp (and header files) as we need to structure our code. - * [GwExampleHardware.h](GwExampleHardware.h) includes our pin definitions for the board. - * [config.json](config.json)
- This file allows to add some config definitions that are needed for our task. For the possible options have a look at the global [config.json](../../web/config.json). Be careful not to overwrite config defitions from the global file. A good practice wood be to prefix the names of definitions with parts of the library name. Always put them in a separate category so that they do not interfere with the system ones. - The defined config items can later be accessed in the code (see the example in [GwExampleTask.cpp](GwExampleTask.cpp)). - - Hints - ----- - Just be careful not to interfere with C symbols from the core - so it is a good practice to prefix your files and class like in the example. - - Developing - ---------- - To develop I recommend forking the gateway repository and adding your own directory below lib (with the string task in it's name). - As your code goes into a separate directory it should be very easy to fetch upstream changes without the need to adapt your code. - Typically after forking the repo on github (https://github.com/wellenvogel/esp32-nmea2000) and initially cloning it you will add my repository as an "upstream repo": - ``` - git remote add upstream https://github.com/wellenvogel/esp32-nmea2000.git - ``` - To merge in a new version use: - ``` - git fetch upstream - git merge upstream/master - ``` - Refer to https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork - - By following the hints in this doc the merge should always succeed without conflicts. - - Future Plans - ------------ - If there will be a need we can extend this extension API by means of adding specific java script code and css for the UI. - diff --git a/lib/obp60task/config.json b/lib/obp60task/config.json index 0a936b4..c7f1756 100644 --- a/lib/obp60task/config.json +++ b/lib/obp60task/config.json @@ -1,474 +1,266 @@ [ { - "name": "obp60Config", - "label": "Logging", - "type": "boolean", - "default": "false", - "description": "Switch on logging of position acquired/failed", - "category": "OBP60 Settings", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "timeZone", - "label": "Time Zone", + "name": "visiblePages", + "label": "number of pages", "type": "number", - "default": "0", - "check": "checkMinMax", - "min": -12, - "max": 12, - "description": "Time zone [UTC -12...+12]", - "category": "OBP60 Settings", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "draft", - "label": "Boat Draft [m]", - "type": "number", - "default": "0", - "check": "checkMinMax", - "min": 0, - "max": 10, - "description": "The draft of the boat [0...10m]", - "category": "OBP60 Settings", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "fuelTank", - "label": "Fuel Tank [l]", - "type": "number", - "default": "0", - "check": "checkMinMax", - "min": 0, - "max": 5000, - "description": "Fuel tank capacity [0...5000l]", - "category": "OBP60 Settings", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "fuelConsumption", - "label": "Fuel Consuption [l/h]", - "type": "number", - "default": "0", - "check": "checkMinMax", - "min": 0, - "max": 1000, - "description": "Medium fuel consumption [0...1000l/h]", - "category": "OBP60 Settings", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "waterTank", - "label": "Water Tank [l]", - "type": "number", - "default": "0", - "check": "checkMinMax", - "min": 0, - "max": 5000, - "description": "Water tank capacity [0...5000l]", - "category": "OBP60 Settings", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "wasteTank", - "label": "Waste Tank [l]", - "type": "number", - "default": "0", - "check": "checkMinMax", - "min": 0, - "max": 5000, - "description": "Water tank capacity [0...5000l]", - "category": "OBP60 Settings", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "batteryVoltage", - "label": "Battery Voltage [V]", - "type": "number", - "default": "12", - "check": "checkMinMax", - "min": 0, - "max": 1000, - "description": "Fuel tank capacity [0...1000V]", - "category": "OBP60 Settings", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "batteryType", - "label": "Battery Type", - "type": "list", - "default": "Pb", - "description": "Type of battery", - "list": [ - "Pb", - "Gel", - "AGM", - "LiFePo4" - ], - "category": "OBP60 Settings", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "batteryCapacity", - "label": "Battery Capacity [Ah]", - "type": "number", - "default": "0", - "check": "checkMinMax", - "min": 0, - "max": 10000, - "description": "Fuel tank capacity [0...10000Ah]", - "category": "OBP60 Settings", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "lengthFormat", - "label": "Length Format", - "type": "list", - "default": "m", - "description": "Length format [m|ft]", - "list": [ - "m", - "ft" - ], - "category": "OBP60 Units", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "distanceFormat", - "label": "Distance Format", - "type": "list", - "default": "m", - "description": "Distance format [m|km|nm]", - "list": [ - "m", - "km", - "nm" - ], - "category": "OBP60 Units", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "speedFormat", - "label": "Speed Format", - "type": "list", - "default": "m/s", - "description": "Distance format [m/s|km/h|kn]", - "list": [ - "m/s", - "km/h", - "kn" - ], - "category": "OBP60 Units", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "windspeedFormat", - "label": "Wind Speed Format", - "type": "list", - "default": "m/s", - "description": "Distance format [m/s|km/h|kn|bft]", - "list": [ - "m/s", - "km/h", - "kn", - "bft" - ], - "category": "OBP60 Units", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "tempFormat", - "label": "Temperature Format", - "type": "list", - "default": "C", - "description": "Length format [K|°C|°F]", - "list": [ - "K", - "C", - "F" - ], - "category": "OBP60 Units", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "dateFormat", - "label": "Date Format", - "type": "list", - "default": "GB", - "description": "Date format [DE|GB|US] DE: 31.12.2022, GB: 31/12/2022, US: 12/31/2022", - "list": [ - "DE", - "GB", - "US" - ], - "category": "OBP60 Units", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "useGPS", - "label": "GPS NEO-6M", - "type": "boolean", - "default": "false", - "description": "Using internal GPS modul NEO-6M", - "category": "OBP60 Hardware", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "useBME280", - "label": "BME280", - "type": "boolean", - "default": "false", - "description": "Using internal BME280 modul", - "category": "OBP60 Hardware", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "use1Wire", - "label": "1Wire", - "type": "boolean", - "default": "false", - "description": "Using external 1Wirew devices (DS18B20)", - "category": "OBP60 Hardware", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "powerMode", - "label": "Power Mode", - "type": "list", - "default": "Max Power", - "description": "Settings for power mode", - "list": [ - "Max Power", - "Only 3.3V", - "Only 5.0V", - "Min Power" - ], - "category": "OBP60 Hardware", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "underVoltage", - "label": "Undervoltage", - "type": "boolean", - "default": "false", - "description": "If undervoltage detection [on|off] lower than 9V then switch off the device", - "category": "OBP60 Hardware", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "useSimuData", - "label": "Simulation Data", - "type": "boolean", - "default": "false", - "description": "Can use for simulation data by missing bus data.", - "category": "OBP60 Hardware", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "display", - "label": "Display Mode", - "type": "list", - "default": "Logo + QR Code", - "description": "Settings for display mode", - "list": [ - "White Screen", - "Logo", - "Logo + QR Code", - "Off" - ], - "category": "OBP60 Display", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "statusLine", - "label": "Status Line", - "type": "boolean", - "default": "true", - "description": "Show status line [on|off]", - "category": "OBP60 Display", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "refresh", - "label": "Refresh", - "type": "boolean", - "default": "false", - "description": "Refresh E-Ink display after each new page request [on|off]. A refresh reduce background shaddows from older pages.", - "category": "OBP60 Display", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "holdvalues", - "label": "Hold Values", - "type": "boolean", - "default": "false", - "description": "Hold old measuring values by missing data stream [on|off]", - "category": "OBP60 Display", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "backlight", - "label": "Backlight Mode", - "type": "list", - "default": "Off", - "description": "Settings for display mode", - "list": [ - "Off", - "Control by Sun", - "Control by Bus", - "Control by Time", - "Control by Key", - "On" - ], - "category": "OBP60 Display", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "flashLED", - "label": "Flash LED Mode", - "type": "list", - "default": "Off", - "description": "Settings for flash LED", - "list": [ - "Off", - "Bus Data", - "GPS Fix", - "Limits Overrun" - ], - "category": "OBP60 Display", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "buzzerError", - "label": "Buzzer Error", - "type": "boolean", - "default": "false", - "description": "Sound on error", - "category": "OBP60 Buzzer", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "buzzerGps", - "label": "Buzzer GPS Fix", - "type": "boolean", - "default": "false", - "description": "Sound on missing or lost GPS fix", - "category": "OBP60 Buzzer", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "buzzerLim", - "label": "Buzzer by Limits", - "type": "boolean", - "default": "false", - "description": "Sound on limit overrun", - "category": "OBP60 Buzzer", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "buzzerMode", - "label": "Buzzer Mode", - "type": "list", - "default": "Off", - "description": "Settings for Buzzer Mode", - "list": [ - "Off", - "Short Single Beep", - "Longer Single Beep", - "Beep until Confirmation" - ], - "category": "OBP60 Buzzer", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "buzzerPower", - "label": "Buzzer Power [%]", - "type": "number", - "default": "50", - "check": "checkMinMax", - "min": 0, - "max": 100, - "description": "Buzzer Loudness [0...100%]", - "category": "OBP60 Buzzer", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "numberPages", - "label": "Number Of Pages", - "type": "number", - "default": "4", "check": "checkMinMax", "min": 1, - "max": 10, - "description": "Number of user pages [1...10]", - "category": "OBP60 Pages", + "max": 4, + "default":"1", + "category":"pagecommon", "capabilities": { - "obp60":"true" + "pagetask":"true" } - } + }, + { + "name": "page1type", + "label": "type", + "type": "list", + "default": "oneValue", + "description": "type of page for page 1", + "list":["oneValue","twoValues","threeValues","forValues","apparentWind"], + "category": "page1", + "capabilities": { + "pagetask":"true" + } + }, + { + "name": "page1value1", + "label": "field1", + "type": "boatData", + "default": "", + "description": "the display for field one", + "category": "page1", + "capabilities": { + "pagetask":"true" + }, + "condition":[{"page1type":"oneValue"},{"page1type":"twoValues"},{"page1type":"threeValues"},{"page1type":"forValues"}] + }, + { + "name": "page1value2", + "label": "field2", + "type": "boatData", + "default": "", + "description": "the display for field two", + "category": "page1", + "capabilities": { + "pagetask":"true" + }, + "condition":[{"page1type":"twoValues"},{"page1type":"threeValues"},{"page1type":"forValues"}] + } + , + { + "name": "page1value3", + "label": "field3", + "type": "boatData", + "default": "", + "description": "the display for field 3", + "category": "page1", + "capabilities": { + "pagetask":"true" + }, + "condition":[{"page1type":"threeValues"},{"page1type":"forValues"}] + } + , + { + "name": "page1value4", + "label": "field4", + "type": "boatData", + "default": "", + "description": "the display for field 4", + "category": "page1", + "capabilities": { + "pagetask":"true" + }, + "condition":[{"page1type":"forValues"}] + }, + { + "name": "page2type", + "label": "type", + "type": "list", + "default": "oneValue", + "description": "type of page for page 1", + "list":["oneValue","twoValues","threeValues","forValues","apparentWind"], + "category": "page2", + "capabilities": { + "pagetask":"true" + } + }, + { + "name": "page2value1", + "label": "field1", + "type": "boatData", + "default": "", + "description": "the display for field one", + "category": "page2", + "capabilities": { + "pagetask":"true" + }, + "condition":[{"page2type":"oneValue"},{"page2type":"twoValues"},{"page2type":"threeValues"},{"page2type":"forValues"}] + }, + { + "name": "page2value2", + "label": "field2", + "type": "boatData", + "default": "", + "description": "the display for field two", + "category": "page2", + "capabilities": { + "pagetask":"true" + }, + "condition":[{"page2type":"twoValues"},{"page2type":"threeValues"},{"page2type":"forValues"}] + } + , + { + "name": "page2value3", + "label": "field3", + "type": "boatData", + "default": "", + "description": "the display for field 3", + "category": "page2", + "capabilities": { + "pagetask":"true" + }, + "condition":[{"page2type":"threeValues"},{"page2type":"forValues"}] + } + , + { + "name": "page2value4", + "label": "field4", + "type": "boatData", + "default": "", + "description": "the display for field 4", + "category": "page2", + "capabilities": { + "pagetask":"true" + }, + "condition":[{"page2type":"forValues"}] + } + , + { + "name": "page3type", + "label": "type", + "type": "list", + "default": "oneValue", + "description": "type of page for page 1", + "list":["oneValue","twoValues","threeValues","forValues","apparentWind"], + "category": "page3", + "capabilities": { + "pagetask":"true" + } + }, + { + "name": "page3value1", + "label": "field1", + "type": "boatData", + "default": "", + "description": "the display for field one", + "category": "page3", + "capabilities": { + "pagetask":"true" + }, + "condition":[{"page3type":"oneValue"},{"page3type":"twoValues"},{"page3type":"threeValues"},{"page3type":"forValues"}] + }, + { + "name": "page3value2", + "label": "field2", + "type": "boatData", + "default": "", + "description": "the display for field two", + "category": "page3", + "capabilities": { + "pagetask":"true" + }, + "condition":[{"page3type":"twoValues"},{"page3type":"threeValues"},{"page3type":"forValues"}] + } + , + { + "name": "page3value3", + "label": "field3", + "type": "boatData", + "default": "", + "description": "the display for field 3", + "category": "page3", + "capabilities": { + "pagetask":"true" + }, + "condition":[{"page3type":"threeValues"},{"page3type":"forValues"}] + } + , + { + "name": "page3value4", + "label": "field4", + "type": "boatData", + "default": "", + "description": "the display for field 4", + "category": "page3", + "capabilities": { + "pagetask":"true" + }, + "condition":[{"page3type":"forValues"}] + } + , + { + "name": "page4type", + "label": "type", + "type": "list", + "default": "oneValue", + "description": "type of page for page 1", + "list":["oneValue","twoValues","threeValues","forValues","apparentWind"], + "category": "page4", + "capabilities": { + "pagetask":"true" + } + }, + { + "name": "page4value1", + "label": "field1", + "type": "boatData", + "default": "", + "description": "the display for field one", + "category": "page4", + "capabilities": { + "pagetask":"true" + }, + "condition":[{"page4type":"oneValue"},{"page4type":"twoValues"},{"page4type":"threeValues"},{"page4type":"forValues"}] + }, + { + "name": "page4value2", + "label": "field2", + "type": "boatData", + "default": "", + "description": "the display for field two", + "category": "page4", + "capabilities": { + "pagetask":"true" + }, + "condition":[{"page4type":"twoValues"},{"page4type":"threeValues"},{"page4type":"forValues"}] + } + , + { + "name": "page4value3", + "label": "field3", + "type": "boatData", + "default": "", + "description": "the display for field 3", + "category": "page4", + "capabilities": { + "pagetask":"true" + }, + "condition":[{"page4type":"threeValues"},{"page4type":"forValues"}] + } + , + { + "name": "page4value4", + "label": "field4", + "type": "boatData", + "default": "", + "description": "the display for field 4", + "category": "page4", + "capabilities": { + "pagetask":"true" + }, + "condition":[{"page4type":"forValues"}] + } + ] diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp new file mode 100644 index 0000000..bc370a3 --- /dev/null +++ b/lib/obp60task/obp60task.cpp @@ -0,0 +1,250 @@ +#ifdef BOARD_NODEMCU32S_OBP60 +#include "obp60task.h" +#include "Pagedata.h" +#include "OBP60Hardware.h" // PIN definitions +#include // Timer Lib for timer interrupts +#include // I2C connections +#include // MCP23017 extension Port +#include +#include +#include +#include // GxEPD lib for E-Ink displays +#include // 4.2" Waveshare S/W 300 x 400 pixel +#include // GxEPD lip for SPI display communikation +#include // GxEPD lip for SPI + +// True type character sets +#include "Ubuntu_Bold8pt7b.h" +#include "Ubuntu_Bold20pt7b.h" +#include "Ubuntu_Bold32pt7b.h" +#include "DSEG7Classic-BoldItalic16pt7b.h" +#include "DSEG7Classic-BoldItalic42pt7b.h" +#include "DSEG7Classic-BoldItalic60pt7b.h" + +// Pictures +//#include GxEPD_BitmapExamples // Example picture +#include "MFD_OBP60_400x300_sw.h" // MFD with logo +#include "Logo_OBP_400x300_sw.h" // OBP Logo + + +void OBP60Init(GwApi *param){ + param->getLogger()->logDebug(GwLog::LOG,"obp60init running"); +} + +typedef struct { + int page0=0; + QueueHandle_t queue; + } MyData; + +void keyboardTask(void *param){ + MyData *data=(MyData *)param; + int page=data->page0; + while (true){ + //send a key event + xQueueSend(data->queue, &page, 0); + delay(10000); + page+=1; + if (page>=MAX_PAGE_NUMBER) page=0; + } + vTaskDelete(NULL); +} + +class BoatValueList{ + public: + static const int MAXVALUES=100; + //we create a list containing all our BoatValues + //this is the list we later use to let the api fill all the values + //additionally we put the necessary values into the paga data - see below + GwApi::BoatValue *allBoatValues[MAXVALUES]; + int numValues=0; + + bool addValueToList(GwApi::BoatValue *v){ + for (int i=0;i= MAXVALUES) return false; + allBoatValues[numValues]=v; + numValues++; + return true; + } + //helper to ensure that each BoatValue is only queried once + GwApi::BoatValue *findValueOrCreate(String name){ + for (int i=0;igetName() == name) { + return allBoatValues[i]; + } + } + GwApi::BoatValue *rt=new GwApi::BoatValue(name); + addValueToList(rt); + return rt; + } +}; + +//we want to have a list that has all our page definitions +//this way each page can easily be added here +//needs some minor tricks for the safe static initialization +typedef std::vector Pages; +//the page list class +class PageList{ + public: + Pages pages; + void add(PageDescription *p){ + pages.push_back(p); + } + PageDescription *find(String name){ + for (auto it=pages.begin();it != pages.end();it++){ + if ((*it)->pageName == name){ + return *it; + } + } + return NULL; + } +}; + +class PageStruct{ + public: + Page *page=NULL; + PageData parameters; + PageDescription *description=NULL; +}; + +/** + * this function will add all the pages we know to the pagelist + * each page should have defined a registerXXXPage variable of type + * PageData that describes what it needs + */ +void registerAllPages(PageList &list){ + //the next line says that this variable is defined somewhere else + //in our case in a separate C++ source file + //this way this separate source file can be compiled by it's own + //and has no access to any of our data except the one that we + //give as a parameter to the page function + extern PageDescription registerPageOneValue; + //we add the variable to our list + list.add(®isterPageOneValue); + extern PageDescription registerPageTwoValues; + list.add(®isterPageTwoValues); + extern PageDescription registerPageThreeValuese; + list.add(®isterPageThreeValuese); + extern PageDescription registerPageForValues; + list.add(®isterPageForValues); + extern PageDescription registerPageApparentWind; + list.add(®isterPageApparentWind); +} + +void OBP60Task(GwApi *api){ + GwLog *logger=api->getLogger(); + GwConfigHandler *config=api->getConfig(); + PageList allPages; + registerAllPages(allPages); + LOG_DEBUG(GwLog::LOG,"obp60task started"); + for (auto it=allPages.pages.begin();it != allPages.pages.end();it++){ + LOG_DEBUG(GwLog::LOG,"found registered page %s",(*it)->pageName.c_str()); + } + int numPages=1; + PageStruct pages[MAX_PAGE_NUMBER]; + CommonData commonData; + commonData.logger=logger; + BoatValueList boatValues; //all the boat values for the api query + //commonData.distanceformat=config->getString(xxx); + //add all necessary data to common data + + //fill the page data from config + numPages=config->getInt(config->visiblePages,1); + if (numPages < 1) numPages=1; + if (numPages >= MAX_PAGE_NUMBER) numPages=MAX_PAGE_NUMBER; + String configPrefix="page"; + for (int i=0;i< numPages;i++){ + String prefix=configPrefix+String(i+1); //e.g. page1 + String configName=prefix+String("type"); + LOG_DEBUG(GwLog::DEBUG,"asking for page config %s",configName.c_str()); + String pageType=config->getString(configName,""); + PageDescription *description=allPages.find(pageType); + if (description == NULL){ + LOG_DEBUG(GwLog::ERROR,"page description for %s not found",pageType.c_str()); + continue; + } + pages[i].description=description; + pages[i].page=description->creator(commonData); + pages[i].parameters.pageName=pageType; + LOG_DEBUG(GwLog::DEBUG,"found page %s for number %d",pageType.c_str(),i); + //fill in all the user defined parameters + for (int uid=0;uiduserParam;uid++){ + String cfgName=prefix+String("value")+String(uid+1); + GwApi::BoatValue *value=boatValues.findValueOrCreate(config->getString(cfgName,"")); + LOG_DEBUG(GwLog::DEBUG,"add user input cfg=%s,value=%s for page %d", + cfgName.c_str(), + value->getName().c_str(), + i + ); + pages[i].parameters.values.push_back(value); + } + //now add the predefined values + for (auto it=description->fixedParam.begin();it != description->fixedParam.end();it++){ + GwApi::BoatValue *value=boatValues.findValueOrCreate(*it); + LOG_DEBUG(GwLog::DEBUG,"added fixed value %s to page %d",value->getName().c_str(),i); + pages[i].parameters.values.push_back(value); + } + } + //now we have prepared the page data + //we start a separate task that will fetch our keys... + MyData allParameters; + allParameters.page0=3; + allParameters.queue=xQueueCreate(10,sizeof(int)); + xTaskCreate(keyboardTask,"keyboard",2000,&allParameters,0,NULL); + + + //loop + LOG_DEBUG(GwLog::LOG,"obp60task: start mainloop"); + int pageNumber=0; + int lastPage=pageNumber; + while (true){ + delay(1000); + //check if there is a keyboard message + int keyboardMessage=-1; + if (xQueueReceive(allParameters.queue,&keyboardMessage,0)){ + LOG_DEBUG(GwLog::LOG,"new page from keyboard %d",keyboardMessage); + Page *currentPage=pages[pageNumber].page; + if (currentPage ){ + keyboardMessage=currentPage->handleKey(keyboardMessage); + } + if (keyboardMessage >= 0 && keyboardMessage < numPages){ + + pageNumber=keyboardMessage; + } + LOG_DEBUG(GwLog::LOG,"set pagenumber to %d",pageNumber); + } + //refresh data from api + api->getBoatDataValues(boatValues.numValues,boatValues.allBoatValues); + api->getStatus(commonData.status); + + //handle the pag + if (pages[pageNumber].description && pages[pageNumber].description->header){ + + //build some header and footer using commonData + } + //.... + //call the particular page + Page *currentPage=pages[pageNumber].page; + if (currentPage == NULL){ + LOG_DEBUG(GwLog::ERROR,"page number %d not found",pageNumber); + } + else{ + if (lastPage != pageNumber){ + currentPage->displayNew(commonData,pages[pageNumber].parameters); + lastPage=pageNumber; + } + //call the page code + LOG_DEBUG(GwLog::DEBUG,"calling page %d",pageNumber); + currentPage->display(commonData,pages[pageNumber].parameters); + } + + } + vTaskDelete(NULL); + +} + +#endif \ No newline at end of file diff --git a/lib/obp60task/obp60task.h b/lib/obp60task/obp60task.h new file mode 100644 index 0000000..7e32e07 --- /dev/null +++ b/lib/obp60task/obp60task.h @@ -0,0 +1,24 @@ +#pragma once +#include "GwApi.h" +//we only compile for some boards +#ifdef BOARD_NODEMCU32S_OBP60 +// CAN NMEA2000 +#define ESP32_CAN_TX_PIN GPIO_NUM_13 +#define ESP32_CAN_RX_PIN GPIO_NUM_12 +// Bus load in 50mA steps +#define N2K_LOAD_LEVEL 5 // 5x50mA = 250mA max bus load with back light on +// RS485 NMEA0183 +#define GWSERIAL_TX 26 +#define GWSERIAL_RX 14 +#define GWSERIAL_MODE "UNI" + +// Init OBP60 Task +void OBP60Init(GwApi *param); +DECLARE_INITFUNCTION(OBP60Init); + +// OBP60 Task +void OBP60Task(GwApi *param); +DECLARE_USERTASK_PARAM(OBP60Task, 25000) // Need 25k RAM as stack size +DECLARE_CAPABILITY(obp60,true); + +#endif \ No newline at end of file diff --git a/lib/obp60task/platformio.ini b/lib/obp60task/platformio.ini index bfc2bb6..e64f1d4 100644 --- a/lib/obp60task/platformio.ini +++ b/lib/obp60task/platformio.ini @@ -17,4 +17,4 @@ build_flags= ${env.build_flags} upload_port = COM3 upload_protocol = esptool -monitor_speed = 115200 +monitor_speed = 115200 \ No newline at end of file diff --git a/lib/obp60task/qrcode.c b/lib/obp60task/qrcode.c deleted file mode 100644 index 0b441b3..0000000 --- a/lib/obp60task/qrcode.c +++ /dev/null @@ -1,876 +0,0 @@ -/** - * The MIT License (MIT) - * - * This library is written and maintained by Richard Moore. - * Major parts were derived from Project Nayuki's library. - * - * Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode) - * Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * Special thanks to Nayuki (https://www.nayuki.io/) from which this library was - * heavily inspired and compared against. - * - * See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp - */ - -#include "qrcode.h" - -#include -#include - -#pragma mark - Error Correction Lookup tables - -#if LOCK_VERSION == 0 - -static const uint16_t NUM_ERROR_CORRECTION_CODEWORDS[4][40] = { - // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - { 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372}, // Medium - { 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750}, // Low - { 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430}, // High - { 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040}, // Quartile -}; - -static const uint8_t NUM_ERROR_CORRECTION_BLOCKS[4][40] = { - // Version: (note that index 0 is for padding, and is set to an illegal value) - // 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - { 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium - { 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low - { 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High - { 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile -}; - -static const uint16_t NUM_RAW_DATA_MODULES[40] = { - // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 208, 359, 567, 807, 1079, 1383, 1568, 1936, 2336, 2768, 3232, 3728, 4256, 4651, 5243, 5867, 6523, - // 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 7211, 7931, 8683, 9252, 10068, 10916, 11796, 12708, 13652, 14628, 15371, 16411, 17483, 18587, - // 32, 33, 34, 35, 36, 37, 38, 39, 40 - 19723, 20891, 22091, 23008, 24272, 25568, 26896, 28256, 29648 -}; - -// @TODO: Put other LOCK_VERSIONS here -#elif LOCK_VERSION == 3 - -static const int16_t NUM_ERROR_CORRECTION_CODEWORDS[4] = { - 26, 15, 44, 36 -}; - -static const int8_t NUM_ERROR_CORRECTION_BLOCKS[4] = { - 1, 1, 2, 2 -}; - -static const uint16_t NUM_RAW_DATA_MODULES = 567; - -#else - -#error Unsupported LOCK_VERSION (add it...) - -#endif - - -static int max(int a, int b) { - if (a > b) { return a; } - return b; -} - -/* -static int abs(int value) { - if (value < 0) { return -value; } - return value; -} -*/ - - -#pragma mark - Mode testing and conversion - -static int8_t getAlphanumeric(char c) { - - if (c >= '0' && c <= '9') { return (c - '0'); } - if (c >= 'A' && c <= 'Z') { return (c - 'A' + 10); } - - switch (c) { - case ' ': return 36; - case '$': return 37; - case '%': return 38; - case '*': return 39; - case '+': return 40; - case '-': return 41; - case '.': return 42; - case '/': return 43; - case ':': return 44; - } - - return -1; -} - -static bool isAlphanumeric(const char *text, uint16_t length) { - while (length != 0) { - if (getAlphanumeric(text[--length]) == -1) { return false; } - } - return true; -} - - -static bool isNumeric(const char *text, uint16_t length) { - while (length != 0) { - char c = text[--length]; - if (c < '0' || c > '9') { return false; } - } - return true; -} - - -#pragma mark - Counting - -// We store the following tightly packed (less 8) in modeInfo -// <=9 <=26 <= 40 -// NUMERIC ( 10, 12, 14); -// ALPHANUMERIC ( 9, 11, 13); -// BYTE ( 8, 16, 16); -static char getModeBits(uint8_t version, uint8_t mode) { - // Note: We use 15 instead of 16; since 15 doesn't exist and we cannot store 16 (8 + 8) in 3 bits - // hex(int("".join(reversed([('00' + bin(x - 8)[2:])[-3:] for x in [10, 9, 8, 12, 11, 15, 14, 13, 15]])), 2)) - unsigned int modeInfo = 0x7bbb80a; - -#if LOCK_VERSION == 0 || LOCK_VERSION > 9 - if (version > 9) { modeInfo >>= 9; } -#endif - -#if LOCK_VERSION == 0 || LOCK_VERSION > 26 - if (version > 26) { modeInfo >>= 9; } -#endif - - char result = 8 + ((modeInfo >> (3 * mode)) & 0x07); - if (result == 15) { result = 16; } - - return result; -} - - -#pragma mark - BitBucket - -typedef struct BitBucket { - uint32_t bitOffsetOrWidth; - uint16_t capacityBytes; - uint8_t *data; -} BitBucket; - -/* -void bb_dump(BitBucket *bitBuffer) { - printf("Buffer: "); - for (uint32_t i = 0; i < bitBuffer->capacityBytes; i++) { - printf("%02x", bitBuffer->data[i]); - if ((i % 4) == 3) { printf(" "); } - } - printf("\n"); -} -*/ - -static uint16_t bb_getGridSizeBytes(uint8_t size) { - return (((size * size) + 7) / 8); -} - -static uint16_t bb_getBufferSizeBytes(uint32_t bits) { - return ((bits + 7) / 8); -} - -static void bb_initBuffer(BitBucket *bitBuffer, uint8_t *data, int32_t capacityBytes) { - bitBuffer->bitOffsetOrWidth = 0; - bitBuffer->capacityBytes = capacityBytes; - bitBuffer->data = data; - - memset(data, 0, bitBuffer->capacityBytes); -} - -static void bb_initGrid(BitBucket *bitGrid, uint8_t *data, uint8_t size) { - bitGrid->bitOffsetOrWidth = size; - bitGrid->capacityBytes = bb_getGridSizeBytes(size); - bitGrid->data = data; - - memset(data, 0, bitGrid->capacityBytes); -} - -static void bb_appendBits(BitBucket *bitBuffer, uint32_t val, uint8_t length) { - uint32_t offset = bitBuffer->bitOffsetOrWidth; - for (int8_t i = length - 1; i >= 0; i--, offset++) { - bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7)); - } - bitBuffer->bitOffsetOrWidth = offset; -} -/* -void bb_setBits(BitBucket *bitBuffer, uint32_t val, int offset, uint8_t length) { - for (int8_t i = length - 1; i >= 0; i--, offset++) { - bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7)); - } -} -*/ -static void bb_setBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool on) { - uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; - uint8_t mask = 1 << (7 - (offset & 0x07)); - if (on) { - bitGrid->data[offset >> 3] |= mask; - } else { - bitGrid->data[offset >> 3] &= ~mask; - } -} - -static void bb_invertBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool invert) { - uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; - uint8_t mask = 1 << (7 - (offset & 0x07)); - bool on = ((bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0); - if (on ^ invert) { - bitGrid->data[offset >> 3] |= mask; - } else { - bitGrid->data[offset >> 3] &= ~mask; - } -} - -static bool bb_getBit(BitBucket *bitGrid, uint8_t x, uint8_t y) { - uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; - return (bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; -} - - -#pragma mark - Drawing Patterns - -// XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical -// properties, calling applyMask(m) twice with the same value is equivalent to no change at all. -// This means it is possible to apply a mask, undo it, and try another mask. Note that a final -// well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). -static void applyMask(BitBucket *modules, BitBucket *isFunction, uint8_t mask) { - uint8_t size = modules->bitOffsetOrWidth; - - for (uint8_t y = 0; y < size; y++) { - for (uint8_t x = 0; x < size; x++) { - if (bb_getBit(isFunction, x, y)) { continue; } - - bool invert = 0; - switch (mask) { - case 0: invert = (x + y) % 2 == 0; break; - case 1: invert = y % 2 == 0; break; - case 2: invert = x % 3 == 0; break; - case 3: invert = (x + y) % 3 == 0; break; - case 4: invert = (x / 3 + y / 2) % 2 == 0; break; - case 5: invert = x * y % 2 + x * y % 3 == 0; break; - case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; - case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; - } - bb_invertBit(modules, x, y, invert); - } - } -} - -static void setFunctionModule(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y, bool on) { - bb_setBit(modules, x, y, on); - bb_setBit(isFunction, x, y, true); -} - -// Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). -static void drawFinderPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) { - uint8_t size = modules->bitOffsetOrWidth; - - for (int8_t i = -4; i <= 4; i++) { - for (int8_t j = -4; j <= 4; j++) { - uint8_t dist = max(abs(i), abs(j)); // Chebyshev/infinity norm - int16_t xx = x + j, yy = y + i; - if (0 <= xx && xx < size && 0 <= yy && yy < size) { - setFunctionModule(modules, isFunction, xx, yy, dist != 2 && dist != 4); - } - } - } -} - -// Draws a 5*5 alignment pattern, with the center module at (x, y). -static void drawAlignmentPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) { - for (int8_t i = -2; i <= 2; i++) { - for (int8_t j = -2; j <= 2; j++) { - setFunctionModule(modules, isFunction, x + j, y + i, max(abs(i), abs(j)) != 1); - } - } -} - -// Draws two copies of the format bits (with its own error correction code) -// based on the given mask and this object's error correction level field. -static void drawFormatBits(BitBucket *modules, BitBucket *isFunction, uint8_t ecc, uint8_t mask) { - - uint8_t size = modules->bitOffsetOrWidth; - - // Calculate error correction code and pack bits - uint32_t data = ecc << 3 | mask; // errCorrLvl is uint2, mask is uint3 - uint32_t rem = data; - for (int i = 0; i < 10; i++) { - rem = (rem << 1) ^ ((rem >> 9) * 0x537); - } - - data = data << 10 | rem; - data ^= 0x5412; // uint15 - - // Draw first copy - for (uint8_t i = 0; i <= 5; i++) { - setFunctionModule(modules, isFunction, 8, i, ((data >> i) & 1) != 0); - } - - setFunctionModule(modules, isFunction, 8, 7, ((data >> 6) & 1) != 0); - setFunctionModule(modules, isFunction, 8, 8, ((data >> 7) & 1) != 0); - setFunctionModule(modules, isFunction, 7, 8, ((data >> 8) & 1) != 0); - - for (int8_t i = 9; i < 15; i++) { - setFunctionModule(modules, isFunction, 14 - i, 8, ((data >> i) & 1) != 0); - } - - // Draw second copy - for (int8_t i = 0; i <= 7; i++) { - setFunctionModule(modules, isFunction, size - 1 - i, 8, ((data >> i) & 1) != 0); - } - - for (int8_t i = 8; i < 15; i++) { - setFunctionModule(modules, isFunction, 8, size - 15 + i, ((data >> i) & 1) != 0); - } - - setFunctionModule(modules, isFunction, 8, size - 8, true); -} - - -// Draws two copies of the version bits (with its own error correction code), -// based on this object's version field (which only has an effect for 7 <= version <= 40). -static void drawVersion(BitBucket *modules, BitBucket *isFunction, uint8_t version) { - - int8_t size = modules->bitOffsetOrWidth; - -#if LOCK_VERSION != 0 && LOCK_VERSION < 7 - return; - -#else - if (version < 7) { return; } - - // Calculate error correction code and pack bits - uint32_t rem = version; // version is uint6, in the range [7, 40] - for (uint8_t i = 0; i < 12; i++) { - rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); - } - - uint32_t data = version << 12 | rem; // uint18 - - // Draw two copies - for (uint8_t i = 0; i < 18; i++) { - bool bit = ((data >> i) & 1) != 0; - uint8_t a = size - 11 + i % 3, b = i / 3; - setFunctionModule(modules, isFunction, a, b, bit); - setFunctionModule(modules, isFunction, b, a, bit); - } - -#endif -} - -static void drawFunctionPatterns(BitBucket *modules, BitBucket *isFunction, uint8_t version, uint8_t ecc) { - - uint8_t size = modules->bitOffsetOrWidth; - - // Draw the horizontal and vertical timing patterns - for (uint8_t i = 0; i < size; i++) { - setFunctionModule(modules, isFunction, 6, i, i % 2 == 0); - setFunctionModule(modules, isFunction, i, 6, i % 2 == 0); - } - - // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) - drawFinderPattern(modules, isFunction, 3, 3); - drawFinderPattern(modules, isFunction, size - 4, 3); - drawFinderPattern(modules, isFunction, 3, size - 4); - -#if LOCK_VERSION == 0 || LOCK_VERSION > 1 - - if (version > 1) { - - // Draw the numerous alignment patterns - - uint8_t alignCount = version / 7 + 2; - uint8_t step; - if (version != 32) { - step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * 2; // ceil((size - 13) / (2*numAlign - 2)) * 2 - } else { // C-C-C-Combo breaker! - step = 26; - } - - uint8_t alignPositionIndex = alignCount - 1; - uint8_t alignPosition[alignCount]; - - alignPosition[0] = 6; - - uint8_t size = version * 4 + 17; - for (uint8_t i = 0, pos = size - 7; i < alignCount - 1; i++, pos -= step) { - alignPosition[alignPositionIndex--] = pos; - } - - for (uint8_t i = 0; i < alignCount; i++) { - for (uint8_t j = 0; j < alignCount; j++) { - if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) { - continue; // Skip the three finder corners - } else { - drawAlignmentPattern(modules, isFunction, alignPosition[i], alignPosition[j]); - } - } - } - } - -#endif - - // Draw configuration data - drawFormatBits(modules, isFunction, ecc, 0); // Dummy mask value; overwritten later in the constructor - drawVersion(modules, isFunction, version); -} - - -// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire -// data area of this QR Code symbol. Function modules need to be marked off before this is called. -static void drawCodewords(BitBucket *modules, BitBucket *isFunction, BitBucket *codewords) { - - uint32_t bitLength = codewords->bitOffsetOrWidth; - uint8_t *data = codewords->data; - - uint8_t size = modules->bitOffsetOrWidth; - - // Bit index into the data - uint32_t i = 0; - - // Do the funny zigzag scan - for (int16_t right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair - if (right == 6) { right = 5; } - - for (uint8_t vert = 0; vert < size; vert++) { // Vertical counter - for (int j = 0; j < 2; j++) { - uint8_t x = right - j; // Actual x coordinate - bool upwards = ((right & 2) == 0) ^ (x < 6); - uint8_t y = upwards ? size - 1 - vert : vert; // Actual y coordinate - if (!bb_getBit(isFunction, x, y) && i < bitLength) { - bb_setBit(modules, x, y, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0); - i++; - } - // If there are any remainder bits (0 to 7), they are already - // set to 0/false/white when the grid of modules was initialized - } - } - } -} - - - -#pragma mark - Penalty Calculation - -#define PENALTY_N1 3 -#define PENALTY_N2 3 -#define PENALTY_N3 40 -#define PENALTY_N4 10 - -// Calculates and returns the penalty score based on state of this QR Code's current modules. -// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. -// @TODO: This can be optimized by working with the bytes instead of bits. -static uint32_t getPenaltyScore(BitBucket *modules) { - uint32_t result = 0; - - uint8_t size = modules->bitOffsetOrWidth; - - // Adjacent modules in row having same color - for (uint8_t y = 0; y < size; y++) { - - bool colorX = bb_getBit(modules, 0, y); - for (uint8_t x = 1, runX = 1; x < size; x++) { - bool cx = bb_getBit(modules, x, y); - if (cx != colorX) { - colorX = cx; - runX = 1; - - } else { - runX++; - if (runX == 5) { - result += PENALTY_N1; - } else if (runX > 5) { - result++; - } - } - } - } - - // Adjacent modules in column having same color - for (uint8_t x = 0; x < size; x++) { - bool colorY = bb_getBit(modules, x, 0); - for (uint8_t y = 1, runY = 1; y < size; y++) { - bool cy = bb_getBit(modules, x, y); - if (cy != colorY) { - colorY = cy; - runY = 1; - } else { - runY++; - if (runY == 5) { - result += PENALTY_N1; - } else if (runY > 5) { - result++; - } - } - } - } - - uint16_t black = 0; - for (uint8_t y = 0; y < size; y++) { - uint16_t bitsRow = 0, bitsCol = 0; - for (uint8_t x = 0; x < size; x++) { - bool color = bb_getBit(modules, x, y); - - // 2*2 blocks of modules having same color - if (x > 0 && y > 0) { - bool colorUL = bb_getBit(modules, x - 1, y - 1); - bool colorUR = bb_getBit(modules, x, y - 1); - bool colorL = bb_getBit(modules, x - 1, y); - if (color == colorUL && color == colorUR && color == colorL) { - result += PENALTY_N2; - } - } - - // Finder-like pattern in rows and columns - bitsRow = ((bitsRow << 1) & 0x7FF) | color; - bitsCol = ((bitsCol << 1) & 0x7FF) | bb_getBit(modules, y, x); - - // Needs 11 bits accumulated - if (x >= 10) { - if (bitsRow == 0x05D || bitsRow == 0x5D0) { - result += PENALTY_N3; - } - if (bitsCol == 0x05D || bitsCol == 0x5D0) { - result += PENALTY_N3; - } - } - - // Balance of black and white modules - if (color) { black++; } - } - } - - // Find smallest k such that (45-5k)% <= dark/total <= (55+5k)% - uint16_t total = size * size; - for (uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) { - result += PENALTY_N4; - } - - return result; -} - - -#pragma mark - Reed-Solomon Generator - -static uint8_t rs_multiply(uint8_t x, uint8_t y) { - // Russian peasant multiplication - // See: https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication - uint16_t z = 0; - for (int8_t i = 7; i >= 0; i--) { - z = (z << 1) ^ ((z >> 7) * 0x11D); - z ^= ((y >> i) & 1) * x; - } - return z; -} - -static void rs_init(uint8_t degree, uint8_t *coeff) { - memset(coeff, 0, degree); - coeff[degree - 1] = 1; - - // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), - // drop the highest term, and store the rest of the coefficients in order of descending powers. - // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). - uint16_t root = 1; - for (uint8_t i = 0; i < degree; i++) { - // Multiply the current product by (x - r^i) - for (uint8_t j = 0; j < degree; j++) { - coeff[j] = rs_multiply(coeff[j], root); - if (j + 1 < degree) { - coeff[j] ^= coeff[j + 1]; - } - } - root = (root << 1) ^ ((root >> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D) - } -} - -static void rs_getRemainder(uint8_t degree, uint8_t *coeff, uint8_t *data, uint8_t length, uint8_t *result, uint8_t stride) { - // Compute the remainder by performing polynomial division - - //for (uint8_t i = 0; i < degree; i++) { result[] = 0; } - //memset(result, 0, degree); - - for (uint8_t i = 0; i < length; i++) { - uint8_t factor = data[i] ^ result[0]; - for (uint8_t j = 1; j < degree; j++) { - result[(j - 1) * stride] = result[j * stride]; - } - result[(degree - 1) * stride] = 0; - - for (uint8_t j = 0; j < degree; j++) { - result[j * stride] ^= rs_multiply(coeff[j], factor); - } - } -} - - - -#pragma mark - QrCode - -static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_t *text, uint16_t length, uint8_t version) { - int8_t mode = MODE_BYTE; - - if (isNumeric((char*)text, length)) { - mode = MODE_NUMERIC; - bb_appendBits(dataCodewords, 1 << MODE_NUMERIC, 4); - bb_appendBits(dataCodewords, length, getModeBits(version, MODE_NUMERIC)); - - uint16_t accumData = 0; - uint8_t accumCount = 0; - for (uint16_t i = 0; i < length; i++) { - accumData = accumData * 10 + ((char)(text[i]) - '0'); - accumCount++; - if (accumCount == 3) { - bb_appendBits(dataCodewords, accumData, 10); - accumData = 0; - accumCount = 0; - } - } - - // 1 or 2 digits remaining - if (accumCount > 0) { - bb_appendBits(dataCodewords, accumData, accumCount * 3 + 1); - } - - } else if (isAlphanumeric((char*)text, length)) { - mode = MODE_ALPHANUMERIC; - bb_appendBits(dataCodewords, 1 << MODE_ALPHANUMERIC, 4); - bb_appendBits(dataCodewords, length, getModeBits(version, MODE_ALPHANUMERIC)); - - uint16_t accumData = 0; - uint8_t accumCount = 0; - for (uint16_t i = 0; i < length; i++) { - accumData = accumData * 45 + getAlphanumeric((char)(text[i])); - accumCount++; - if (accumCount == 2) { - bb_appendBits(dataCodewords, accumData, 11); - accumData = 0; - accumCount = 0; - } - } - - // 1 character remaining - if (accumCount > 0) { - bb_appendBits(dataCodewords, accumData, 6); - } - - } else { - bb_appendBits(dataCodewords, 1 << MODE_BYTE, 4); - bb_appendBits(dataCodewords, length, getModeBits(version, MODE_BYTE)); - for (uint16_t i = 0; i < length; i++) { - bb_appendBits(dataCodewords, (char)(text[i]), 8); - } - } - - //bb_setBits(dataCodewords, length, 4, getModeBits(version, mode)); - - return mode; -} - -static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket *data) { - - // See: http://www.thonky.com/qr-code-tutorial/structure-final-message - -#if LOCK_VERSION == 0 - uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1]; - uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1]; - uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1]; -#else - uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc]; - uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc]; - uint16_t moduleCount = NUM_RAW_DATA_MODULES; -#endif - - uint8_t blockEccLen = totalEcc / numBlocks; - uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks; - uint8_t shortBlockLen = moduleCount / 8 / numBlocks; - - uint8_t shortDataBlockLen = shortBlockLen - blockEccLen; - - uint8_t result[data->capacityBytes]; - memset(result, 0, sizeof(result)); - - uint8_t coeff[blockEccLen]; - rs_init(blockEccLen, coeff); - - uint16_t offset = 0; - uint8_t *dataBytes = data->data; - - - // Interleave all short blocks - for (uint8_t i = 0; i < shortDataBlockLen; i++) { - uint16_t index = i; - uint8_t stride = shortDataBlockLen; - for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) { - result[offset++] = dataBytes[index]; - -#if LOCK_VERSION == 0 || LOCK_VERSION >= 5 - if (blockNum == numShortBlocks) { stride++; } -#endif - index += stride; - } - } - - // Version less than 5 only have short blocks -#if LOCK_VERSION == 0 || LOCK_VERSION >= 5 - { - // Interleave long blocks - uint16_t index = shortDataBlockLen * (numShortBlocks + 1); - uint8_t stride = shortDataBlockLen; - for (uint8_t blockNum = 0; blockNum < numBlocks - numShortBlocks; blockNum++) { - result[offset++] = dataBytes[index]; - - if (blockNum == 0) { stride++; } - index += stride; - } - } -#endif - - // Add all ecc blocks, interleaved - uint8_t blockSize = shortDataBlockLen; - for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) { - -#if LOCK_VERSION == 0 || LOCK_VERSION >= 5 - if (blockNum == numShortBlocks) { blockSize++; } -#endif - rs_getRemainder(blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks); - dataBytes += blockSize; - } - - memcpy(data->data, result, data->capacityBytes); - data->bitOffsetOrWidth = moduleCount; -} - -// We store the Format bits tightly packed into a single byte (each of the 4 modes is 2 bits) -// The format bits can be determined by ECC_FORMAT_BITS >> (2 * ecc) -static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0); - - -#pragma mark - Public QRCode functions - -uint16_t qrcode_getBufferSize(uint8_t version) { - return bb_getGridSizeBytes(4 * version + 17); -} - -// @TODO: Return error if data is too big. -int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length) { - uint8_t size = version * 4 + 17; - qrcode->version = version; - qrcode->size = size; - qrcode->ecc = ecc; - qrcode->modules = modules; - - uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * ecc)) & 0x03; - -#if LOCK_VERSION == 0 - uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1]; - uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1]; -#else - version = LOCK_VERSION; - uint16_t moduleCount = NUM_RAW_DATA_MODULES; - uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits]; -#endif - - struct BitBucket codewords; - uint8_t codewordBytes[bb_getBufferSizeBytes(moduleCount)]; - bb_initBuffer(&codewords, codewordBytes, (int32_t)sizeof(codewordBytes)); - - // Place the data code words into the buffer - int8_t mode = encodeDataCodewords(&codewords, data, length, version); - - if (mode < 0) { return -1; } - qrcode->mode = mode; - - // Add terminator and pad up to a byte if applicable - uint32_t padding = (dataCapacity * 8) - codewords.bitOffsetOrWidth; - if (padding > 4) { padding = 4; } - bb_appendBits(&codewords, 0, padding); - bb_appendBits(&codewords, 0, (8 - codewords.bitOffsetOrWidth % 8) % 8); - - // Pad with alternate bytes until data capacity is reached - for (uint8_t padByte = 0xEC; codewords.bitOffsetOrWidth < (dataCapacity * 8); padByte ^= 0xEC ^ 0x11) { - bb_appendBits(&codewords, padByte, 8); - } - - BitBucket modulesGrid; - bb_initGrid(&modulesGrid, modules, size); - - BitBucket isFunctionGrid; - uint8_t isFunctionGridBytes[bb_getGridSizeBytes(size)]; - bb_initGrid(&isFunctionGrid, isFunctionGridBytes, size); - - // Draw function patterns, draw all codewords, do masking - drawFunctionPatterns(&modulesGrid, &isFunctionGrid, version, eccFormatBits); - performErrorCorrection(version, eccFormatBits, &codewords); - drawCodewords(&modulesGrid, &isFunctionGrid, &codewords); - - // Find the best (lowest penalty) mask - uint8_t mask = 0; - int32_t minPenalty = INT32_MAX; - for (uint8_t i = 0; i < 8; i++) { - drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, i); - applyMask(&modulesGrid, &isFunctionGrid, i); - int penalty = getPenaltyScore(&modulesGrid); - if (penalty < minPenalty) { - mask = i; - minPenalty = penalty; - } - applyMask(&modulesGrid, &isFunctionGrid, i); // Undoes the mask due to XOR - } - - qrcode->mask = mask; - - // Overwrite old format bits - drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, mask); - - // Apply the final choice of mask - applyMask(&modulesGrid, &isFunctionGrid, mask); - - return 0; -} - -int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data) { - return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, strlen(data)); -} - -bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) { - if (x < 0 || x >= qrcode->size || y < 0 || y >= qrcode->size) { - return false; - } - - uint32_t offset = y * qrcode->size + x; - return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; -} - -/* -uint8_t qrcode_getHexLength(QRCode *qrcode) { - return ((qrcode->size * qrcode->size) + 7) / 4; -} - -void qrcode_getHex(QRCode *qrcode, char *result) { - -} -*/ diff --git a/lib/obp60task/qrcode.h b/lib/obp60task/qrcode.h deleted file mode 100644 index 6a5745e..0000000 --- a/lib/obp60task/qrcode.h +++ /dev/null @@ -1,99 +0,0 @@ -/** - * The MIT License (MIT) - * - * This library is written and maintained by Richard Moore. - * Major parts were derived from Project Nayuki's library. - * - * Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode) - * Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * Special thanks to Nayuki (https://www.nayuki.io/) from which this library was - * heavily inspired and compared against. - * - * See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp - */ - - -#ifndef __QRCODE_H_ -#define __QRCODE_H_ - -#ifndef __cplusplus -typedef unsigned char bool; -static const bool false = 0; -static const bool true = 1; -#endif - -#include - - -// QR Code Format Encoding -#define MODE_NUMERIC 0 -#define MODE_ALPHANUMERIC 1 -#define MODE_BYTE 2 - - -// Error Correction Code Levels -#define ECC_LOW 0 -#define ECC_MEDIUM 1 -#define ECC_QUARTILE 2 -#define ECC_HIGH 3 - - -// If set to non-zero, this library can ONLY produce QR codes at that version -// This saves a lot of dynamic memory, as the codeword tables are skipped -#ifndef LOCK_VERSION -#define LOCK_VERSION 0 -#endif - - -typedef struct QRCode { - uint8_t version; - uint8_t size; - uint8_t ecc; - uint8_t mode; - uint8_t mask; - uint8_t *modules; -} QRCode; - - -#ifdef __cplusplus -extern "C"{ -#endif /* __cplusplus */ - - - -uint16_t qrcode_getBufferSize(uint8_t version); - -int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data); -int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length); - -bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y); - - - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - - -#endif /* __QRCODE_H_ */