Merge branch 'new_concept'

This commit is contained in:
norbert-walter 2022-02-03 15:55:29 +01:00
commit 92c96b256a
24 changed files with 788 additions and 2833 deletions

View File

@ -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 <Ticker.h> // Timer Lib for timer interrupts
#include <Wire.h> // I2C connections
#include <MCP23017.h> // MCP23017 extension Port
#include <NMEA0183.h>
#include <NMEA0183Msg.h>
#include <NMEA0183Messages.h>
#include <GxEPD.h> // GxEPD lib for E-Ink displays
#include <GxGDEW042T2/GxGDEW042T2.h> // 4.2" Waveshare S/W 300 x 400 pixel
#include <GxIO/GxIO_SPI/GxIO_SPI.h> // GxEPD lip for SPI display communikation
#include <GxIO/GxIO.h> // 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

View File

@ -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

View File

@ -1,122 +0,0 @@
#ifndef _OBP60Data_H
#define _OBP60Data_H
#include <Arduino.h>
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

View File

@ -1,106 +0,0 @@
#ifndef _OBP60EXTENSIONPORT_H
#define _OBP60EXTENSIONPORT_H
#include <Arduino.h>
#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

View File

@ -1,103 +0,0 @@
#ifndef _OBP60FUNCTIONS_H
#define _OBP60FUNCTIONS_H
#include <Arduino.h>
#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

View File

@ -1,172 +0,0 @@
#ifndef _OBP60PAGES_H
#define _OBP60PAGES_H
#include <Arduino.h>
// 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

View File

@ -1,48 +0,0 @@
#ifndef _OBP60QRWIFI_H
#define _OBP60QRWIFI_H
#include <Arduino.h>
#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

View File

@ -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
);

View File

@ -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
);

View File

@ -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
);

View File

@ -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
);

View File

@ -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
);

View File

@ -1,66 +0,0 @@
#ifndef _PAGE_0_H
#define _PAGE_0_H
#include <Arduino.h>
#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

View File

@ -1,33 +0,0 @@
#ifndef _PAGE_1_H
#define _PAGE_1_H
#include <Arduino.h>
#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

View File

@ -1,32 +0,0 @@
#ifndef _PAGE_2_H
#define _PAGE_2_H
#include <Arduino.h>
#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

View File

@ -1,51 +0,0 @@
#ifndef _PAGE_3_H
#define _PAGE_3_H
#include <Arduino.h>
#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

68
lib/obp60task/Pagedata.h Normal file
View File

@ -0,0 +1,68 @@
#pragma once
#include <Arduino.h>
#include "GwApi.h"
#include <functional>
#include <vector>
#define MAX_PAGE_NUMBER 4
typedef std::vector<GwApi::BoatValue *> 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<Page* (CommonData &)> PageFunction;
typedef std::vector<String> 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;
}
};

View File

@ -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)<br>
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)<br>
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.

View File

@ -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"}]
}
]

250
lib/obp60task/obp60task.cpp Normal file
View File

@ -0,0 +1,250 @@
#ifdef BOARD_NODEMCU32S_OBP60
#include "obp60task.h"
#include "Pagedata.h"
#include "OBP60Hardware.h" // PIN definitions
#include <Ticker.h> // Timer Lib for timer interrupts
#include <Wire.h> // I2C connections
#include <MCP23017.h> // MCP23017 extension Port
#include <NMEA0183.h>
#include <NMEA0183Msg.h>
#include <NMEA0183Messages.h>
#include <GxEPD.h> // GxEPD lib for E-Ink displays
#include <GxGDEW042T2/GxGDEW042T2.h> // 4.2" Waveshare S/W 300 x 400 pixel
#include <GxIO/GxIO_SPI/GxIO_SPI.h> // GxEPD lip for SPI display communikation
#include <GxIO/GxIO.h> // 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<numValues;i++){
if (allBoatValues[i] == v){
//already in list...
return true;
}
}
if (numValues >= 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;i<numValues;i++){
if (allBoatValues[i]->getName() == 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<PageDescription*> 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(&registerPageOneValue);
extern PageDescription registerPageTwoValues;
list.add(&registerPageTwoValues);
extern PageDescription registerPageThreeValuese;
list.add(&registerPageThreeValuese);
extern PageDescription registerPageForValues;
list.add(&registerPageForValues);
extern PageDescription registerPageApparentWind;
list.add(&registerPageApparentWind);
}
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;uid<description->userParam;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

24
lib/obp60task/obp60task.h Normal file
View File

@ -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

View File

@ -17,4 +17,4 @@ build_flags=
${env.build_flags}
upload_port = COM3
upload_protocol = esptool
monitor_speed = 115200
monitor_speed = 115200

View File

@ -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 <stdlib.h>
#include <string.h>
#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) {
}
*/

View File

@ -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 <stdint.h>
// 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_ */