Răsfoiți Sursa

added initial sw

Per Mårtensson 4 ani în urmă
părinte
comite
daf545fb77

+ 39 - 0
sw/key800/boards/key800.json

@@ -0,0 +1,39 @@
+{
+      "build": {
+      "arduino":{
+        "ldscript": "esp32s2_out.ld"
+      },
+      "core": "esp32",
+      "f_cpu": "240000000L",
+      "f_flash": "80000000L",
+      "flash_mode": "qio",
+      "boot": "qio",
+      "mcu": "esp32s2",
+      "variant": "key800"
+    },
+    "connectivity": [
+      "wifi"
+    ],
+    "debug": {
+      "openocd_target": "esp32s2.cfg"
+    },
+    "frameworks": [
+      "espidf",
+      "arduino"
+    ],
+    "bootloader": {
+      "file": "/home/pm/project/abc80/key800/sw/key800/bootloader_qio_80m.bin"
+    },
+    "name": "key800",
+    "upload": {
+      "flash_size": "4MB",
+      "maximum_ram_size": 327680,
+      "maximum_size": 4194304,
+      "require_upload_port": true,
+      "speed": 460800
+    },
+    "url": "https://git.sweproj.com/ABC80/key800",
+    "vendor": "SweProj"
+  
+}
+  

+ 99 - 0
sw/key800/boards/key800/pins_arduino.h

@@ -0,0 +1,99 @@
+#ifndef Pins_Arduino_h
+#define Pins_Arduino_h
+
+#include <stdint.h>
+
+
+#define USB_VID 0x239A
+#define USB_PID 0x80DF
+#define USB_MANUFACTURER "SweProj"
+#define USB_PRODUCT "KEY800 ESP32-S2"
+#define USB_SERIAL ""
+
+
+#define EXTERNAL_NUM_INTERRUPTS 46
+#define NUM_DIGITAL_PINS        48
+#define NUM_ANALOG_INPUTS       20
+
+#define analogInputToDigitalPin(p)  (((p)<20)?(esp32_adc2gpio[(p)]):-1)
+#define digitalPinToInterrupt(p)    (((p)<48)?(p):-1)
+#define digitalPinHasPWM(p)         (p < 46)
+/*
+static const uint8_t PIN_NEOPIXEL = 1;  // D1
+static const uint8_t NEOPIXEL_POWER = 21;
+
+static const uint8_t LED_BUILTIN = 13;
+
+static const uint8_t EPD_BUSY = 5;
+static const uint8_t EPD_RESET = 6;
+static const uint8_t EPD_DC = 7;
+static const uint8_t EPD_CS = 8;
+
+static const uint8_t ACCEL_IRQ = 9;
+
+static const uint8_t BUTTON_A = 15;
+static const uint8_t BUTTON_B = 14;
+static const uint8_t BUTTON_C = 12;
+static const uint8_t BUTTON_D = 11;
+
+static const uint8_t LIGHT_SENSOR = 3;
+static const uint8_t BATT_MONITOR = 4;
+static const uint8_t SPEAKER_SHUTDOWN = 16;
+
+static const uint8_t SDA = 33;
+static const uint8_t SCL = 34;
+
+static const uint8_t SS    = 8;
+static const uint8_t MOSI  = 35;
+static const uint8_t SCK   = 36;
+static const uint8_t MISO  = 37;
+
+*/
+
+
+
+static const uint8_t TX = 37;
+static const uint8_t RX = 38;
+
+
+static const uint8_t A0 = 17;
+static const uint8_t A1 = 18;
+static const uint8_t A2 = 1;
+static const uint8_t A3 = 2;
+static const uint8_t A4 = 3;
+static const uint8_t A5 = 4;
+static const uint8_t A6 = 5;
+static const uint8_t A7 = 6;
+static const uint8_t A8 = 7;
+static const uint8_t A9 = 8;
+static const uint8_t A10 = 9;
+static const uint8_t A11 = 10;
+static const uint8_t A12 = 11;
+static const uint8_t A13 = 12;
+static const uint8_t A14 = 13;
+static const uint8_t A15 = 14;
+static const uint8_t A16 = 15;
+static const uint8_t A17 = 16;
+static const uint8_t A18 = 19;
+static const uint8_t A19 = 20;
+
+
+static const uint8_t T1 = 1;
+static const uint8_t T2 = 2;
+static const uint8_t T3 = 3;
+static const uint8_t T4 = 4;
+static const uint8_t T5 = 5;
+static const uint8_t T6 = 6;
+static const uint8_t T7 = 7;
+static const uint8_t T8 = 8;
+static const uint8_t T9 = 9;
+static const uint8_t T10 = 10;
+static const uint8_t T11 = 11;
+static const uint8_t T12 = 12;
+static const uint8_t T13 = 13;
+static const uint8_t T14 = 14;
+
+static const uint8_t DAC1 = 17;
+static const uint8_t DAC2 = 18;
+
+#endif /* Pins_Arduino_h */

BIN
sw/key800/bootloader_qio_80m.bin


Fișier diff suprimat deoarece este prea mare
+ 23 - 0
sw/key800/get-platformio.py


+ 308 - 0
sw/key800/include/ESPAsyncWiFiManager.h

@@ -0,0 +1,308 @@
+/**************************************************************
+   WiFiManager is a library for the ESP8266/Arduino platform
+   (https://github.com/esp8266/Arduino) to enable easy
+   configuration and reconfiguration of WiFi credentials using a Captive Portal
+   inspired by:
+   http://www.esp8266.com/viewtopic.php?f=29&t=2520
+   https://github.com/chriscook8/esp-arduino-apboot
+   https://github.com/esp8266/Arduino/tree/esp8266/hardware/esp8266com/esp8266/libraries/DNSServer/examples/CaptivePortalAdvanced
+   Built by AlexT https://github.com/tzapu
+   Ported to Async Web Server by https://github.com/alanswx
+   Licensed under MIT license
+ **************************************************************/
+
+#ifndef ESPAsyncWiFiManager_h
+#define ESPAsyncWiFiManager_h
+
+#if defined(ESP8266)
+#include <ESP8266WiFi.h> // https://github.com/esp8266/Arduino
+#else
+#include <WiFi.h>
+#include "esp_wps.h"
+#define ESP_WPS_MODE WPS_TYPE_PBC
+#endif
+#include <ESPAsyncWebServer.h>
+
+//#define USE_EADNS               // uncomment to use ESPAsyncDNSServer
+#ifdef USE_EADNS
+#include <ESPAsyncDNSServer.h> // https://github.com/devyte/ESPAsyncDNSServer
+                               // https://github.com/me-no-dev/ESPAsyncUDP
+#else
+#include <DNSServer.h>
+#endif
+#include <memory>
+
+// fix crash on ESP32 (see https://github.com/alanswx/ESPAsyncWiFiManager/issues/44)
+#if defined(ESP8266)
+typedef int wifi_ssid_count_t;
+#else
+typedef int16_t wifi_ssid_count_t;
+#endif
+
+#if defined(ESP8266)
+extern "C"
+{
+#include "user_interface.h"
+}
+#else
+#include <rom/rtc.h>
+#endif
+
+const char WFM_HTTP_HEAD[] PROGMEM = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/><title>{v}</title>";
+const char HTTP_STYLE[] PROGMEM = "<style>.c{text-align: center;} div,input{padding:5px;font-size:1em;} input{width:95%;} body{text-align: center;font-family:verdana;} button{border:0;border-radius:0.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;} .q{float: right;width: 64px;text-align: right;} .l{background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAALVBMVEX///8EBwfBwsLw8PAzNjaCg4NTVVUjJiZDRUUUFxdiZGSho6OSk5Pg4eFydHTCjaf3AAAAZElEQVQ4je2NSw7AIAhEBamKn97/uMXEGBvozkWb9C2Zx4xzWykBhFAeYp9gkLyZE0zIMno9n4g19hmdY39scwqVkOXaxph0ZCXQcqxSpgQpONa59wkRDOL93eAXvimwlbPbwwVAegLS1HGfZAAAAABJRU5ErkJggg==\") no-repeat left center;background-size: 1em;}</style>";
+const char HTTP_SCRIPT[] PROGMEM = "<script>function c(l){document.getElementById('s').value=l.innerText||l.textContent;document.getElementById('p').focus();}</script>";
+const char HTTP_HEAD_END[] PROGMEM = "</head><body><div style='text-align:left;display:inline-block;min-width:260px;'>";
+const char HTTP_PORTAL_OPTIONS[] PROGMEM = "<form action=\"/wifi\" method=\"get\"><button>Configure WiFi</button></form><br/><form action=\"/0wifi\" method=\"get\"><button>Configure WiFi (No Scan)</button></form><br/><form action=\"/i\" method=\"get\"><button>Info</button></form><br/><form action=\"/r\" method=\"post\"><button>Reset</button></form>";
+const char HTTP_ITEM[] PROGMEM = "<div><a href='#p' onclick='c(this)'>{v}</a>&nbsp;<span class='q {i}'>{r}%</span></div>";
+const char HTTP_FORM_START[] PROGMEM = "<form method='get' action='wifisave'><input id='s' name='s' length=32 placeholder='SSID'><br/><input id='p' name='p' length=64 type='password' placeholder='password'><br/>";
+const char HTTP_FORM_PARAM[] PROGMEM = "<br/><input id='{i}' name='{n}' length={l} placeholder='{p}' value='{v}' {c}>";
+const char HTTP_FORM_END[] PROGMEM = "<br/><button type='submit'>save</button></form>";
+const char HTTP_SCAN_LINK[] PROGMEM = "<br/><div class=\"c\"><a href=\"/wifi\">Scan</a></div>";
+const char HTTP_SAVED[] PROGMEM = "<div>Credentials Saved<br />Trying to connect ESP to network.<br />If it fails reconnect to AP to try again</div>";
+const char HTTP_END[] PROGMEM = "</div></body></html>";
+
+#define WIFI_MANAGER_MAX_PARAMS 10
+
+class AsyncWiFiManagerParameter
+{
+public:
+  AsyncWiFiManagerParameter(const char *custom);
+  AsyncWiFiManagerParameter(const char *id,
+                            const char *placeholder,
+                            const char *defaultValue,
+                            unsigned int length);
+  AsyncWiFiManagerParameter(const char *id,
+                            const char *placeholder,
+                            const char *defaultValue,
+                            unsigned int length,
+                            const char *custom);
+
+  const char *getID();
+  const char *getValue();
+  const char *getPlaceholder();
+  unsigned int getValueLength();
+  const char *getCustomHTML();
+
+private:
+  const char *_id;
+  const char *_placeholder;
+  char *_value;
+  unsigned int _length;
+  const char *_customHTML;
+
+  void init(const char *id,
+            const char *placeholder,
+            const char *defaultValue,
+            unsigned int length,
+            const char *custom);
+
+  friend class AsyncWiFiManager;
+};
+
+class WiFiResult
+{
+public:
+  bool duplicate;
+  String SSID;
+  uint8_t encryptionType;
+  int32_t RSSI;
+  uint8_t *BSSID;
+  int32_t channel;
+  bool isHidden;
+
+  WiFiResult()
+  {
+  }
+};
+
+class AsyncWiFiManager
+{
+public:
+#ifdef USE_EADNS
+  AsyncWiFiManager(AsyncWebServer *server, AsyncDNSServer *dns);
+#else
+  AsyncWiFiManager(AsyncWebServer *server, DNSServer *dns);
+#endif
+
+  void scan(boolean async = false);
+  String scanModal();
+  void loop();
+  void safeLoop();
+  void criticalLoop();
+  String infoAsString();
+
+  boolean autoConnect(unsigned long maxConnectRetries = 1,
+                      unsigned long retryDelayMs = 1000);
+  boolean autoConnect(char const *apName,
+                      char const *apPassword = NULL,
+                      unsigned long maxConnectRetries = 1,
+                      unsigned long retryDelayMs = 1000);
+
+  // if you want to always start the config portal, without trying to connect first
+  boolean startConfigPortal(char const *apName, char const *apPassword = NULL);
+  void startConfigPortalModeless(char const *apName, char const *apPassword);
+
+  // get the AP name of the config portal, so it can be used in the callback
+  String getConfigPortalSSID();
+
+  void resetSettings();
+
+  // sets timeout before webserver loop ends and exits even if there has been no setup.
+  // usefully for devices that failed to connect at some point and got stuck in a webserver loop.
+  // in seconds, setConfigPortalTimeout is a new name for setTimeout
+  void setConfigPortalTimeout(unsigned long seconds);
+  void setTimeout(unsigned long seconds);
+
+  // sets timeout for which to attempt connecting, usefull if you get a lot of failed connects
+  void setConnectTimeout(unsigned long seconds);
+
+  // wether or not the wifi manager tries to connect to configured access point even when
+  // configuration portal (ESP as access point) is running [default true/on]
+  void setTryConnectDuringConfigPortal(boolean v);
+
+  void setDebugOutput(boolean debug);
+  // defaults to not showing anything under 8% signal quality if called
+  void setMinimumSignalQuality(unsigned int quality = 8);
+  // sets a custom ip /gateway /subnet configuration
+  void setAPStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn);
+  // sets config for a static IP
+  void setSTAStaticIPConfig(IPAddress ip,
+                            IPAddress gw,
+                            IPAddress sn,
+                            IPAddress dns1 = (uint32_t)0x00000000,
+                            IPAddress dns2 = (uint32_t)0x00000000);
+  // called when AP mode and config portal is started
+  void setAPCallback(std::function<void(AsyncWiFiManager *)>);
+  // called when settings have been changed and connection was successful
+  void setSaveConfigCallback(std::function<void()> func);
+  void setConnectCallback(std::function<void()> func);
+  void setDisconnectCallback(std::function<void()> func);
+  //adds a custom parameter
+  void addParameter(AsyncWiFiManagerParameter *p);
+  // if this is set, it will exit after config, even if connection is unsucessful
+  void setBreakAfterConfig(boolean shouldBreak);
+  // if this is set, try WPS setup when starting (this will delay config portal for up to 2 mins)
+  // TODO
+  // if this is set, customise style
+  void setCustomHeadElement(const char *element);
+  // if this is true, remove duplicated Access Points - defaut true
+  void setRemoveDuplicateAPs(boolean removeDuplicates);
+  // sets a custom element to add to options page
+  void setCustomOptionsElement(const char *element);
+
+  String getConfiguredSTASSID(){
+      return _ssid;
+  }
+  String getConfiguredSTAPassword(){
+      return _pass;
+  }
+  void handleWifi(AsyncWebServerRequest *, boolean scan);
+  void handleWifiSave(AsyncWebServerRequest *);
+  void setupConfig();
+private:
+  AsyncWebServer *server;
+#ifdef USE_EADNS
+  AsyncDNSServer *dnsServer;
+#else
+  DNSServer *dnsServer;
+#endif
+
+  boolean _modeless;
+  unsigned long scannow;
+  boolean shouldscan = true;
+  boolean needInfo = true;
+
+  //const int     WM_DONE                 = 0;
+  //const int     WM_WAIT                 = 10;
+  //const String  HTTP_HEAD = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/><title>{v}</title>";
+
+  void setupConfigPortal();
+  
+#ifdef NO_EXTRA_4K_HEAP
+  void startWPS();
+#endif
+  String pager;
+  wl_status_t wifiStatus;
+  const char *_apName = "no-net";
+  const char *_apPassword = NULL;
+  String _ssid = "";
+  String _pass = "";
+  unsigned long _configPortalTimeout = 0;
+  unsigned long _connectTimeout = 0;
+  unsigned long _configPortalStart = 0;
+
+  IPAddress _ap_static_ip;
+  IPAddress _ap_static_gw;
+  IPAddress _ap_static_sn;
+  IPAddress _sta_static_ip;
+  IPAddress _sta_static_gw;
+  IPAddress _sta_static_sn;
+  IPAddress _sta_static_dns1 = (uint32_t)0x00000000;
+  IPAddress _sta_static_dns2 = (uint32_t)0x00000000;
+
+  unsigned int _paramsCount = 0;
+  unsigned int _minimumQuality = 0;
+  boolean _removeDuplicateAPs = true;
+  boolean _shouldBreakAfterConfig = false;
+#ifdef NO_EXTRA_4K_HEAP
+  boolean _tryWPS = false;
+#endif
+  const char *_customHeadElement = "";
+  const char *_customOptionsElement = "";
+
+  //String        getEEPROMString(int start, int len);
+  //void          setEEPROMString(int start, int len, String string);
+
+  uint8_t status = WL_IDLE_STATUS;
+  uint8_t connectWifi(String ssid, String pass);
+  uint8_t waitForConnectResult();
+  void setInfo();
+  void copySSIDInfo(wifi_ssid_count_t n);
+  String networkListAsString();
+
+  void handleRoot(AsyncWebServerRequest *);
+
+  void handleInfo(AsyncWebServerRequest *);
+  void handleReset(AsyncWebServerRequest *);
+  void handleNotFound(AsyncWebServerRequest *);
+  boolean captivePortal(AsyncWebServerRequest *);
+
+  // DNS server
+  const byte DNS_PORT = 53;
+
+  // helpers
+  unsigned int getRSSIasQuality(int RSSI);
+  boolean isIp(String str);
+  String toStringIp(IPAddress ip);
+
+  boolean connect;
+  boolean _debug = true;
+
+  WiFiResult *wifiSSIDs;
+  wifi_ssid_count_t wifiSSIDCount;
+  boolean wifiSSIDscan;
+
+  boolean _tryConnectDuringConfigPortal = true;
+
+  std::function<void(AsyncWiFiManager *)> _apcallback;
+  std::function<void()> _savecallback;
+  std::function<void()> _connectcallback;
+  std::function<void()> _disconnectcallback;
+  AsyncWiFiManagerParameter *_params[WIFI_MANAGER_MAX_PARAMS];
+
+  template <typename Generic>
+  void DEBUG_WM(Generic text);
+
+  template <class T>
+  auto optionalIPFromString(T *obj, const char *s) -> decltype(obj->fromString(s))
+  {
+    return obj->fromString(s);
+  }
+  auto optionalIPFromString(...) -> bool
+  {
+    DEBUG_WM(F("NO fromString METHOD ON IPAddress, you need ESP8266 core 2.1.0 or newer for Custom IP configuration to work."));
+    return false;
+  }
+};
+
+#endif

+ 12 - 0
sw/key800/include/abc800_callback.h

@@ -0,0 +1,12 @@
+#ifndef __ABC800_CALLBACK_H__
+#define __ABC800_CALLBACK_H__
+#include "config.h"
+
+namespace abc800 {
+	class callback {
+	public:
+
+		void wificonnect();
+	};
+}
+#endif // __ABC80_CALLBACK_H__

+ 74 - 0
sw/key800/include/abc800_config.h

@@ -0,0 +1,74 @@
+#ifndef __ABC800_DEFS_H__
+#define __ABC800_DEFS_H__
+
+#include <Arduino.h>
+
+#define TRUE 1
+#define FALSE 0
+// Software config
+#define KEY800_FWVERSION    1
+#define KEY800_HWVERSION 	101
+
+// Serial port settings
+#define KEY800_SERIAL_BAUD_RATE 115200
+
+
+//Logging
+#define KEY800_LOGGING_ENABLE_SERIAL
+#ifndef KEY800_LOGGING_SERIAL_PORT
+#define KEY800_LOGGING_SERIAL_PORT 		Serial
+#endif
+#define KEY800_LOGGING_VERBOSE_LEVEL 	5
+
+//WIFI
+#ifndef ABC800_WIFI_RETRY_TIMEOUT
+#define ABC800_WIFI_RETRY_TIMEOUT 		00
+#endif
+
+#ifndef ABC800_WIFI_TIMEOUT_MS 
+#define ABC800_WIFI_TIMEOUT_MS			10000 
+#endif 
+
+#ifndef ABC800_WIFI_RECOVER_TIME_MS 
+#define ABC800_WIFI_RECOVER_TIME_MS 		30000 
+#endif 
+
+#define ABC800_REPLACE_CHAR 				0x00
+#define ABC800_REPLACE_FUNCTION 			0x01
+
+#define ABC800_KEY_OUT_BUFFER_SIZE		2048
+#define ABC800_KEY_IN_BUFFER_SIZE		256
+#define ABC800_KEY_REPEAT_TIME			100
+#define ABC800_KEY_CHECK_TIME			50
+#define ABC800_KEY_REPEAT_TIME_DELAY		500
+
+#define ABC800_KEY_WIFI_SSID				"ABC800-KEY800"
+#define ABC800_KEY_WIFI_PSK				"key800!!!"
+#define ABC800_KEY_WIFI_HOSTNAME			"ABC800-KEY800"
+#define ABC800_KEY_SPIFFS_CONFIG_PART  	"data"
+
+#define ABC800_KEY_IN_ABC800				0x00
+#define ABC800_KEY_DELAY_LEGACY			4
+#define ABC800_KEY_DELAY					4		
+
+#define ABC800_KEY_SPIFFS_PATH			"/"
+#define ABC800_KEY_SPIFFS_PARTITION		"data"
+
+struct key800config
+{
+	char * ssid;
+	char * psk;
+};
+struct key800queue
+{
+	uint8_t 	ascii;
+	uint8_t 	modifier;
+	uint8_t 	special;
+};
+struct customreplace
+{
+	uint8_t		abc800key;
+	uint8_t		function;
+	uint8_t		replace; 
+};
+#endif

+ 33 - 0
sw/key800/include/abc800_func.h

@@ -0,0 +1,33 @@
+#ifndef __ABC800_FUNC_H__
+#define __ABC800_FUNC_H__
+#include <Arduino.h>
+struct utf8decodedata
+{
+	uint8_t		state;
+	uint8_t		left;
+	uint8_t		acc;
+	uint8_t		data;
+	uint8_t		error;
+
+};
+namespace abc800 {
+	class std_abc800
+	{
+	public:
+		void reverse8(uint8_t *buf, int size, uint8_t *rev);
+		String hex_to_string(uint8_t *buf, int size, String separator = "");
+		void serial_empty(HardwareSerial *port);
+		int hex_to_int(char c);
+		int hex_to_ascii(char c, char d);
+		void hex2bin(uint8_t *out, const char *in, size_t *size);
+		void getmac_b(String mac, uint8_t *mac_b);
+		String hexaddr(uint8_t *buf);
+		uint8_t processutf8String(const uint8_t* s,uint8_t len,uint8_t* d);
+	private:
+		void processutf8(uint8_t b,utf8decodedata *data);
+
+	};
+
+}
+
+#endif // __ABC80__STD_H__

+ 39 - 0
sw/key800/include/abc800_gpio.h

@@ -0,0 +1,39 @@
+#ifndef __ABC800_GPIO_H__
+#define __ABC800_GPIO_H__
+#include <Arduino.h>
+
+#define K800_RST            2
+#define K800_KD             3
+#define K800_TXD            8
+#define K800_RXD            6
+#define K800_TRXC           9
+
+#define A800_RST            34
+#define A800_KD             33
+#define A800_TXD            5
+#define A800_RXD            7
+#define A800_TRXC           4
+
+#define KEY800_LED          18
+#define KEY800_RST_BUTTON   14
+#define KEY800_UP_BUTTON    13
+#define KEY800_DOWN_BUTTON  12
+
+#define KEY800_PWM_CHANNEL  0
+#define KEY800_PWM_FREQ     10000
+#define KEY800_PWM_RES      10
+#define KEY800_PWM_DUTY     512
+namespace abc800 {
+	class gpio
+	{
+        public:
+            void init();
+            uint8_t sendkey(uint8_t key);
+            uint8_t abc800_getkey();
+            uint8_t abc800_getupdown();
+
+    };
+}
+
+
+#endif 

+ 150 - 0
sw/key800/include/abc800_hidboot.h

@@ -0,0 +1,150 @@
+/* Copyright (C) 2011 Circuits At Home, LTD. All rights reserved.
+
+This software may be distributed and modified under the terms of the GNU
+General Public License version 2 (GPL2) as published by the Free Software
+Foundation and appearing in the file GPL2.TXT included in the packaging of
+this file. Please note that GPL2 Section 2[b] requires that all works based
+on this software must also be made publicly available under the terms of
+the GPL2 ("Copyleft").
+
+Contact information
+-------------------
+
+Circuits At Home, LTD
+Web      :  http://www.circuitsathome.com
+e-mail   :  support@circuitsathome.com
+*/
+
+#ifndef HIDBOOT_H_INCLUDED
+#define HIDBOOT_H_INCLUDED
+
+#include <stdint.h>
+#include "keyboardpipe.h"
+
+#define KEY_SPACE					0x2c
+#define KEY_ZERO					0x27
+#define KEY_ZERO2					0x62
+#define KEY_ENTER					0x28
+#define KEY_PERIOD					0x63
+#define KEY_LESSMORE				0x64
+#define KEY_DELETE					0x4c
+/**
+ * \brief MOUSEINFO definition.
+ */
+struct MOUSEINFO
+{
+	struct
+	{
+		uint8_t		bmLeftButton	: 1;
+		uint8_t		bmRightButton	: 1;
+		uint8_t		bmMiddleButton	: 1;
+		uint8_t		bmDummy			: 1;
+	};
+	int8_t			dX;
+	int8_t			dY;
+};
+
+
+
+/**
+ * \brief MODIFIERKEYS definition.
+ */
+struct MODIFIERKEYS
+{
+	uint8_t		bmLeftCtrl		: 1;
+	uint8_t		bmLeftShift		: 1;
+	uint8_t		bmLeftAlt		: 1;
+	uint8_t		bmLeftGUI		: 1;
+	uint8_t		bmRightCtrl		: 1;
+	uint8_t		bmRightShift	: 1;
+	uint8_t		bmRightAlt		: 1;
+	uint8_t		bmRightGUI		: 1;
+};
+
+/**
+ * \brief KBDINFO definition.
+ */
+struct KBDINFO
+{
+	struct
+	{
+		uint8_t		bmLeftCtrl		: 1;
+		uint8_t		bmLeftShift		: 1;
+		uint8_t		bmLeftAlt		: 1;
+		uint8_t		bmLeftGUI		: 1;
+		uint8_t		bmRightCtrl		: 1;
+		uint8_t		bmRightShift	: 1;
+		uint8_t		bmRightAlt		: 1;
+		uint8_t		bmRightGUI		: 1;
+	};
+	uint8_t			bReserved;
+	uint8_t			Keys[6];
+};
+
+/**
+ * \brief KBDLEDS definition.
+ */
+struct KBDLEDS
+{
+	uint8_t		bmNumLock		: 1;
+	uint8_t		bmCapsLock		: 1;
+	uint8_t		bmScrollLock	: 1;
+	uint8_t		bmCompose		: 1;
+	uint8_t		bmKana			: 1;
+	uint8_t		bmReserved		: 3;
+};
+
+#define KEY_NUM_LOCK				0x53
+
+// Clear compiler warning
+#ifdef KEY_CAPS_LOCK
+#undef KEY_CAPS_LOCK
+#endif
+
+#define KEY_CAPS_LOCK				0x39
+#define KEY_SCROLL_LOCK				0x47
+class HIDReportParser
+{
+public:
+	virtual void Parse(uint32_t len, uint8_t *buf,USBHostKeyboard *port) = 0;
+};
+/**
+ * \class KeyboardReportParser definition.
+ */
+class KeyboardReportParser : public HIDReportParser
+{
+	static const uint8_t numKeys[];
+	static const uint8_t symKeysUp[];
+	static const uint8_t symKeysLo[];
+	static const uint8_t padKeys[];
+
+protected:
+	union
+	{
+		KBDINFO		kbdInfo;
+		uint8_t		bInfo[sizeof(KBDINFO)];
+	}	prevState;
+
+	union
+	{
+		KBDLEDS		kbdLeds;
+		uint8_t		bLeds;
+	}	kbdLockingKeys;
+
+	
+
+public:
+	KeyboardReportParser() { kbdLockingKeys.bLeds = 0; };
+
+	virtual void Parse(uint32_t len, uint8_t *buf,USBHostKeyboard *port);
+	uint8_t OemToAscii(uint8_t mod, uint8_t key);
+	uint8_t OemToABC80(uint8_t mod, uint8_t key);
+protected:
+	uint8_t HandleLockingKeys(uint8_t key,USBHostKeyboard *port);
+
+	virtual void OnKeyDown	(uint8_t mod, uint8_t key)	{};
+	virtual void OnKeyUp	(uint8_t mod, uint8_t key)	{};
+};
+
+
+#endif /* HIDBOOT_H_INCLUDED */

+ 19 - 0
sw/key800/include/abc800_keyboard.h

@@ -0,0 +1,19 @@
+#ifndef __ABC800_KEYBOARD_H__
+#define __ABC800_KEYBOARD_H__
+#include <Arduino.h>
+#include "abc800_config.h"
+namespace abc800 {
+	class keyboard {
+	    public:
+		    void init();
+            uint8_t getABC800Keycode(key800queue keyqueue);
+            uint8_t specialABC800Keycode(key800queue keyqueue);
+            key800queue getKeyboardKeycode(uint8_t abc800key);
+            uint8_t repeat_abc800_key();
+            uint8_t check_abc800_key ();
+            uint8_t abc800_old_key; 
+        private:
+            
+	};
+}
+#endif 

+ 13 - 0
sw/key800/include/abc800_littlefs.h

@@ -0,0 +1,13 @@
+#ifndef __ABC800_LITTLEFS_H__
+#define __ABC800_LITTLEFS__H__
+#include <Arduino.h>
+#include "abc800_config.h"
+namespace abc800 {
+	class littlefs {
+        public:
+            void setup();
+            key800config loadconfig();
+        private:
+    };
+}
+#endif

+ 9 - 0
sw/key800/include/abc800_usb.h

@@ -0,0 +1,9 @@
+#ifndef __ABC800_USB_H__
+#define __ABC800_USB_H__
+#include "port.h"
+
+void ctrl_pipe_cb(ext_pipe_event_msg_t event, usb_irp_t *irp);
+void abc800_port_init();
+void  onRepeatTimer_out(TimerHandle_t xTimer);
+
+#endif

+ 29 - 0
sw/key800/include/abc800_web.h

@@ -0,0 +1,29 @@
+#ifndef __ABC800_OTA_H
+#define __ABC800_OTA_H
+#include <Arduino.h>
+#include <ESPAsyncWebServer.h>
+namespace abc800
+{
+    void OTAtask(void * parameter);
+    class web
+    {
+        public:
+            void init();
+            void sendwebserial(uint8_t test);
+            bool connected = false;
+            void fbuttonsinit();
+            void initWebSocket();
+            
+        private:
+            void notifyClients();
+            void handleWebSocketMessage(void *arg, uint8_t *data, size_t len);
+            void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type,void *arg, uint8_t *data, size_t len);
+            
+            String processor(const String& var);
+            
+            
+            
+    };
+}
+
+#endif

+ 144 - 0
sw/key800/include/abc800_webcontent.h

@@ -0,0 +1,144 @@
+#ifndef __ABC800_WEBCONTENT_H
+#define __ABC800_WEBCONTENT_H
+#include <ArduinoOTA.h>
+const char index_html[] PROGMEM = R"rawliteral(
+<!DOCTYPE HTML><html>
+<head>
+  <title>ABC800 keyboard controller</title>
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <link rel="icon" href="data:,">
+  <style>
+  html {
+    font-family: Arial, Helvetica, sans-serif;
+    text-align: center;
+  }
+  h1 {
+    font-size: 1.8rem;
+    color: white;
+  }
+  h2{
+    font-size: 1.5rem;
+    font-weight: bold;
+    color: #143642;
+  }
+  .topnav {
+    overflow: hidden;
+    background-color: #143642;
+  }
+  body {
+    margin: 0;
+  }
+  .content {
+    padding: 30px;
+    max-width: 600px;
+    margin: 0 auto;
+  }
+  .card {
+    background-color: #F8F7F9;;
+    box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
+    padding-top:10px;
+    padding-bottom:20px;
+  }
+  .button {
+    padding: 15px 50px;
+    font-size: 24px;
+    text-align: center;
+    outline: none;
+    color: #fff;
+    background-color: #0f8b8d;
+    border: none;
+    border-radius: 5px;
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    -webkit-tap-highlight-color: rgba(0,0,0,0);
+   }
+   /*.button:hover {background-color: #0f8b8d}*/
+   .button:active {
+     background-color: #0f8b8d;
+     box-shadow: 2 2px #CDCDCD;
+     transform: translateY(2px);
+   }
+   .state {
+     font-size: 1.5rem;
+     color:#8c8c8c;
+     font-weight: bold;
+   }
+  </style>
+<title>ABC80 Keyboard controller</title>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="icon" href="data:,">
+</head>
+<body>
+  <div class="topnav">
+    <h1>ABC80 Keyboard controller</h1>
+  </div>
+  <div class="content">
+    <div class="card">
+      <h2>Output - GPIO 2</h2>
+      <p class="state">state: <span id="state">%STATE%</span></p>
+      <p><button id="button" class="button">Toggle</button></p>
+    </div>
+  </div>
+<script>
+  var gateway = `ws://${window.location.hostname}/ws`;
+  var websocket;
+  window.addEventListener('load', onLoad);
+  function initWebSocket() {
+    console.log('Trying to open a WebSocket connection...');
+    websocket = new WebSocket(gateway);
+    websocket.onopen    = onOpen;
+    websocket.onclose   = onClose;
+    websocket.onmessage = onMessage; // <-- add this line
+  }
+  function onOpen(event) {
+    console.log('Connection opened');
+  }
+  function onClose(event) {
+    console.log('Connection closed');
+    setTimeout(initWebSocket, 2000);
+  }
+  function onMessage(event) {
+    var state;
+    if (event.data == "1"){
+      state = "ON";
+    }
+    else{
+      state = "OFF";
+    }
+    document.getElementById('state').innerHTML = state;
+  }
+  function onLoad(event) {
+    initWebSocket();
+    initButton();
+  }
+  function initButton() {
+    document.getElementById('button').addEventListener('click', toggle);
+  }
+  function toggle(){
+    websocket.send('toggle');
+  }
+</script>
+</body>
+</html>)rawliteral";
+const char fbuttons_html[] PROGMEM = R"rawliteral(<!DOCTYPE HTML><html><head>
+  <title>KEY80 Input Form</title>
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  </head><body>
+  <form action="/fbuttonsget">
+    F1:  <textarea name="input1" rows="15" cols="60"></textarea>
+    <input type="submit" value="Submit">
+  </form><br>
+  <form action="/fbuttonsget">
+    F2:  <textarea name="input2" rows="15" cols="60"></textarea>
+    <input type="submit" value="Submit">
+  </form><br>
+    <form action="/fbuttonsget">
+    F3:  <textarea name="input3" rows="15" cols="60"></textarea>
+    <input type="submit" value="Submit">
+  </form><br>
+</body></html>)rawliteral";
+#endif

+ 18 - 0
sw/key800/include/abc800_wifi.h

@@ -0,0 +1,18 @@
+#ifndef __ABC800_WIFI_H__
+#define __ABC800_WIFI_H__
+#include <Arduino.h>
+#include "abc800_config.h"
+#include <WiFi.h>
+#include <ESPAsyncWiFiManager.h> 
+namespace abc800 {
+	class wifi {
+		public:
+			void setup();
+		private:
+
+	};
+
+}
+
+
+#endif // __ABC800_WIFI_H__

+ 63 - 0
sw/key800/key800.code-workspace

@@ -0,0 +1,63 @@
+{
+	"folders": [
+		{
+			"path": "."
+		}
+	],
+	"settings": {
+		"files.associations": {
+			"array": "cpp",
+			"deque": "cpp",
+			"string": "cpp",
+			"unordered_map": "cpp",
+			"unordered_set": "cpp",
+			"vector": "cpp",
+			"string_view": "cpp",
+			"initializer_list": "cpp",
+			"*.tcc": "cpp",
+			"memory": "cpp",
+			"random": "cpp",
+			"type_traits": "cpp",
+			"fstream": "cpp",
+			"istream": "cpp",
+			"ostream": "cpp",
+			"sstream": "cpp",
+			"cstddef": "cpp",
+			"functional": "cpp",
+			"optional": "cpp",
+			"system_error": "cpp",
+			"regex": "cpp",
+			"tuple": "cpp",
+			"utility": "cpp",
+			"*.cppxxx": "cpp",
+			"atomic": "cpp",
+			"bitset": "cpp",
+			"cctype": "cpp",
+			"clocale": "cpp",
+			"cmath": "cpp",
+			"cstdarg": "cpp",
+			"cstdint": "cpp",
+			"cstdio": "cpp",
+			"cstdlib": "cpp",
+			"cstring": "cpp",
+			"ctime": "cpp",
+			"cwchar": "cpp",
+			"cwctype": "cpp",
+			"exception": "cpp",
+			"algorithm": "cpp",
+			"iterator": "cpp",
+			"map": "cpp",
+			"memory_resource": "cpp",
+			"numeric": "cpp",
+			"iomanip": "cpp",
+			"iosfwd": "cpp",
+			"iostream": "cpp",
+			"limits": "cpp",
+			"new": "cpp",
+			"stdexcept": "cpp",
+			"streambuf": "cpp",
+			"cinttypes": "cpp",
+			"typeinfo": "cpp"
+		}
+	}
+}

+ 38 - 0
sw/key800/platformio.ini

@@ -0,0 +1,38 @@
+[platformio]
+
+boards_dir = boards
+
+[env:key800]
+platform = https://github.com/platformio/platform-espressif32.git#feature/arduino-upstream
+
+
+board_bootloader.file = bootloader_qio_80m.bin
+board = key800
+framework = arduino
+platform_packages = 
+    platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#master
+    #2.0.0-rc1
+board_build.mcu = esp32s2
+description = SweProj KEY800
+lib_deps =
+    https://git.sweproj.com/pm/esp32S2-usb-host-library.git
+    me-no-dev/AsyncTCP
+    https://github.com/me-no-dev/ESPAsyncWebServer.git#master
+    https://git.sweproj.com/pm/AsyncElegantOTAKEY80.git#master
+    https://git.sweproj.com/pm/WebSerialKEY80.git
+    https://github.com/bblanchon/ArduinoJson.git#5.x
+    https://github.com/lorol/LITTLEFS.git#master
+    https://git.sweproj.com/pm/ElegantOTAKEY80.git#master
+
+
+upload_port = /dev/ttyUSB0
+monitor_port = /dev/ttyUSB0
+monitor_speed = 115200
+
+build_type = debug
+board_build.partitions = variants/key800/partitions.csv
+BUILD_FLAGS =  -I$PROJECT_DIR/boards/key800 -w 
+    '-DCORE_HAS_LIBB64'
+    '-DCORE_DEBUG_LEVEL=5'
+    '-DLOG_LOCAL_LEVEL=5'
+    '-DHWVER=101'

+ 1391 - 0
sw/key800/src/ESPAsyncWiFiManager.cpp

@@ -0,0 +1,1391 @@
+/**************************************************************
+   AsyncWiFiManager is a library for the ESP8266/Arduino platform
+   (https://github.com/esp8266/Arduino) to enable easy
+   configuration and reconfiguration of WiFi credentials using a Captive Portal
+   inspired by:
+   http://www.esp8266.com/viewtopic.php?f=29&t=2520
+   https://github.com/chriscook8/esp-arduino-apboot
+   https://github.com/esp8266/Arduino/tree/esp8266/hardware/esp8266com/esp8266/libraries/DNSServer/examples/CaptivePortalAdvanced
+   Built by AlexT https://github.com/tzapu
+   Ported to Async Web Server by https://github.com/alanswx
+   Licensed under MIT license
+ **************************************************************/
+#include <Arduino.h>
+#include "ESPAsyncWiFiManager.h"
+#include "abc800_web.h"
+#include <ESPAsyncWebServer.h>
+#include <AsyncElegantOTA.h>
+#include <WebSerial.h>
+extern abc800::web abc800_web;
+extern AsyncElegantOtaClass AsyncElegantOTA;
+AsyncWiFiManagerParameter::AsyncWiFiManagerParameter(const char *custom)
+{
+  _id = NULL;
+  _placeholder = NULL;
+  _length = 0;
+  _value = NULL;
+
+  _customHTML = custom;
+}
+
+AsyncWiFiManagerParameter::AsyncWiFiManagerParameter(const char *id,
+                                                     const char *placeholder,
+                                                     const char *defaultValue,
+                                                     unsigned int length)
+{
+  init(id, placeholder, defaultValue, length, "");
+}
+
+AsyncWiFiManagerParameter::AsyncWiFiManagerParameter(const char *id,
+                                                     const char *placeholder,
+                                                     const char *defaultValue,
+                                                     unsigned int length,
+                                                     const char *custom)
+{
+  init(id, placeholder, defaultValue, length, custom);
+}
+
+void AsyncWiFiManagerParameter::init(const char *id,
+                                     const char *placeholder,
+                                     const char *defaultValue,
+                                     unsigned int length,
+                                     const char *custom)
+{
+  _id = id;
+  _placeholder = placeholder;
+  _length = length;
+  _value = new char[length + 1];
+
+  for (unsigned int i = 0; i < length; i++)
+  {
+    _value[i] = 0;
+  }
+  if (defaultValue != NULL)
+  {
+    strncpy(_value, defaultValue, length);
+  }
+
+  _customHTML = custom;
+}
+
+const char *AsyncWiFiManagerParameter::getValue()
+{
+  return _value;
+}
+const char *AsyncWiFiManagerParameter::getID()
+{
+  return _id;
+}
+const char *AsyncWiFiManagerParameter::getPlaceholder()
+{
+  return _placeholder;
+}
+unsigned int AsyncWiFiManagerParameter::getValueLength()
+{
+  return _length;
+}
+const char *AsyncWiFiManagerParameter::getCustomHTML()
+{
+  return _customHTML;
+}
+
+#ifdef USE_EADNS
+AsyncWiFiManager::AsyncWiFiManager(AsyncWebServer *server,
+                                   AsyncDNSServer *dns) : server(server), dnsServer(dns)
+{
+#else
+AsyncWiFiManager::AsyncWiFiManager(AsyncWebServer *server,
+                                   DNSServer *dns) : server(server), dnsServer(dns)
+{
+#endif
+  wifiSSIDs = NULL;
+  wifiSSIDscan = true;
+  _modeless = false;
+  shouldscan = true;
+}
+
+void AsyncWiFiManager::addParameter(AsyncWiFiManagerParameter *p)
+{
+  _params[_paramsCount] = p;
+  _paramsCount++;
+  DEBUG_WM(F("Adding parameter"));
+  DEBUG_WM(p->getID());
+}
+void AsyncWiFiManager::setupConfig()
+{
+  server->on("/wifi",
+             std::bind(&AsyncWiFiManager::handleWifi, this, std::placeholders::_1, true));
+  server->on("/0wifi",
+             std::bind(&AsyncWiFiManager::handleWifi, this, std::placeholders::_1, false));
+  server->on("/wifisave",
+             std::bind(&AsyncWiFiManager::handleWifiSave, this, std::placeholders::_1));
+}
+void AsyncWiFiManager::setupConfigPortal()
+{
+  // dnsServer.reset(new DNSServer());
+  // server.reset(new ESP8266WebServer(80));
+  server->reset();
+
+  DEBUG_WM(F(""));
+  _configPortalStart = millis();
+
+  DEBUG_WM(F("Configuring access point... "));
+  DEBUG_WM(_apName);
+  if (_apPassword != NULL)
+  {
+    if (strlen(_apPassword) < 8 || strlen(_apPassword) > 63)
+    {
+      // fail passphrase to short or long!
+      DEBUG_WM(F("Invalid AccessPoint password. Ignoring"));
+      _apPassword = NULL;
+    }
+    DEBUG_WM(_apPassword);
+  }
+
+  // optional soft ip config
+  if (_ap_static_ip)
+  {
+    DEBUG_WM(F("Custom AP IP/GW/Subnet"));
+    WiFi.softAPConfig(_ap_static_ip, _ap_static_gw, _ap_static_sn);
+  }
+
+  if (_apPassword != NULL)
+  {
+    WiFi.softAP(_apName, _apPassword); // password option
+  }
+  else
+  {
+    WiFi.softAP(_apName);
+  }
+
+  delay(500); // without delay I've seen the IP address blank
+  DEBUG_WM(F("AP IP address: "));
+  DEBUG_WM(WiFi.softAPIP());
+
+// setup the DNS server redirecting all the domains to the apIP
+#ifdef USE_EADNS
+  dnsServer->setErrorReplyCode(AsyncDNSReplyCode::NoError);
+#else
+  dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
+#endif
+  if (!dnsServer->start(DNS_PORT, "*", WiFi.softAPIP()))
+  {
+    DEBUG_WM(F("Could not start Captive DNS Server!"));
+  }
+
+  setInfo();
+
+  // setup web pages: root, wifi config pages, SO captive portal detectors and not found
+  server->on("/",
+             std::bind(&AsyncWiFiManager::handleRoot, this, std::placeholders::_1))
+      .setFilter(ON_AP_FILTER);
+  server->on("/wifi",
+             std::bind(&AsyncWiFiManager::handleWifi, this, std::placeholders::_1, true))
+      .setFilter(ON_AP_FILTER);
+  server->on("/0wifi",
+             std::bind(&AsyncWiFiManager::handleWifi, this, std::placeholders::_1, false))
+      .setFilter(ON_AP_FILTER);
+  server->on("/wifisave",
+             std::bind(&AsyncWiFiManager::handleWifiSave, this, std::placeholders::_1))
+      .setFilter(ON_AP_FILTER);
+  server->on("/i",
+             std::bind(&AsyncWiFiManager::handleInfo, this, std::placeholders::_1))
+      .setFilter(ON_AP_FILTER);
+  server->on("/r",
+             std::bind(&AsyncWiFiManager::handleReset, this, std::placeholders::_1))
+      .setFilter(ON_AP_FILTER);
+  server->on("/fwlink",
+             std::bind(&AsyncWiFiManager::handleRoot, this, std::placeholders::_1))
+      .setFilter(ON_AP_FILTER); // Microsoft captive portal. Maybe not needed. Might be handled by notFound handler.
+  server->onNotFound(std::bind(&AsyncWiFiManager::handleNotFound, this, std::placeholders::_1));
+  server->begin(); // web server start
+  AsyncElegantOTA.begin(server);
+  WebSerial.begin(server);
+  //WebSerial.msgCallback(WSrecvMsg);
+  abc800_web.fbuttonsinit();
+        /*
+  DEBUG_WM(F("HTTP server started"));
+  */
+}
+
+static const char HEX_CHAR_ARRAY[17] = "0123456789ABCDEF";
+
+#if !defined(ESP8266)
+/**
+* convert char array (hex values) to readable string by seperator
+* buf:           buffer to convert
+* length:        data length
+* strSeperator   seperator between each hex value
+* return:        formated value as String
+*/
+static String byteToHexString(uint8_t *buf, uint8_t length, String strSeperator = "-")
+{
+  String dataString = "";
+  for (uint8_t i = 0; i < length; i++)
+  {
+    byte v = buf[i] / 16;
+    byte w = buf[i] % 16;
+    if (i > 0)
+    {
+      dataString += strSeperator;
+    }
+    dataString += String(HEX_CHAR_ARRAY[v]);
+    dataString += String(HEX_CHAR_ARRAY[w]);
+  }
+  dataString.toUpperCase();
+  return dataString;
+} // byteToHexString
+
+String getESP32ChipID()
+{
+  uint64_t chipid;
+  chipid = ESP.getEfuseMac(); // the chip ID is essentially its MAC address (length: 6 bytes)
+  uint8_t chipid_size = 6;
+  uint8_t chipid_arr[chipid_size];
+  for (uint8_t i = 0; i < chipid_size; i++)
+  {
+    chipid_arr[i] = (chipid >> (8 * i)) & 0xff;
+  }
+  return byteToHexString(chipid_arr, chipid_size, "");
+}
+#endif
+
+boolean AsyncWiFiManager::autoConnect(unsigned long maxConnectRetries,
+                                      unsigned long retryDelayMs)
+{
+  String ssid = "ESP";
+#if defined(ESP8266)
+  ssid += String(ESP.getChipId());
+#else
+  ssid += getESP32ChipID();
+#endif
+  return autoConnect(ssid.c_str(), NULL);
+}
+
+boolean AsyncWiFiManager::autoConnect(char const *apName,
+                                      char const *apPassword,
+                                      unsigned long maxConnectRetries,
+                                      unsigned long retryDelayMs)
+{
+  DEBUG_WM(F(""));
+
+  // attempt to connect; should it fail, fall back to AP
+  WiFi.mode(WIFI_STA);
+
+  for (unsigned long tryNumber = 0; tryNumber < maxConnectRetries; tryNumber++)
+  {
+    DEBUG_WM(F("AutoConnect Try No.:"));
+    DEBUG_WM(tryNumber);
+
+    if (connectWifi("", "") == WL_CONNECTED)
+    {
+      DEBUG_WM(F("IP Address:"));
+      DEBUG_WM(WiFi.localIP());
+      if (_connectcallback!=NULL){
+        _connectcallback();
+      }
+      // connected
+      return true;
+    }
+
+    if (tryNumber + 1 < maxConnectRetries)
+    {
+      // we might connect during the delay
+      unsigned long restDelayMs = retryDelayMs;
+      while (restDelayMs != 0)
+      {
+        if (WiFi.status() == WL_CONNECTED)
+        {
+          DEBUG_WM(F("IP Address (connected during delay):"));
+          DEBUG_WM(WiFi.localIP());
+          if (_connectcallback!=NULL){
+            _connectcallback();
+          }
+          return true;
+        }
+        unsigned long thisDelay = std::min(restDelayMs, 100ul);
+        delay(thisDelay);
+        restDelayMs -= thisDelay;
+      }
+    }
+  }
+
+  return startConfigPortal(apName, apPassword);
+}
+
+String AsyncWiFiManager::networkListAsString()
+{
+  String pager;
+  // display networks in page
+  for (int i = 0; i < wifiSSIDCount; i++)
+  {
+    if (wifiSSIDs[i].duplicate == true)
+    {
+      continue; // skip dups
+    }
+    unsigned int quality = getRSSIasQuality(wifiSSIDs[i].RSSI);
+
+    if (_minimumQuality == 0 || _minimumQuality < quality)
+    {
+      String item = FPSTR(HTTP_ITEM);
+      String rssiQ;
+      rssiQ += quality;
+      item.replace("{v}", wifiSSIDs[i].SSID);
+      item.replace("{r}", rssiQ);
+#if defined(ESP8266)
+      if (wifiSSIDs[i].encryptionType != ENC_TYPE_NONE)
+      {
+#else
+      if (wifiSSIDs[i].encryptionType != WIFI_AUTH_OPEN)
+      {
+#endif
+        item.replace("{i}", "l");
+      }
+      else
+      {
+        item.replace("{i}", "");
+      }
+      pager += item;
+    }
+    else
+    {
+      DEBUG_WM(F("Skipping due to quality"));
+    }
+  }
+  return pager;
+}
+
+String AsyncWiFiManager::scanModal()
+{
+  shouldscan = true;
+  scan();
+  String pager = networkListAsString();
+  return pager;
+}
+
+void AsyncWiFiManager::scan(boolean async)
+{
+  if (!shouldscan)
+  {
+    return;
+  }
+  DEBUG_WM(F("About to scan()"));
+  if (wifiSSIDscan)
+  {
+    wifi_ssid_count_t n = WiFi.scanNetworks(async);
+    copySSIDInfo(n);
+  }
+}
+
+void AsyncWiFiManager::copySSIDInfo(wifi_ssid_count_t n)
+{
+  if (n == WIFI_SCAN_FAILED)
+  {
+    DEBUG_WM(F("scanNetworks returned: WIFI_SCAN_FAILED!"));
+  }
+  else if (n == WIFI_SCAN_RUNNING)
+  {
+    DEBUG_WM(F("scanNetworks returned: WIFI_SCAN_RUNNING!"));
+  }
+  else if (n < 0)
+  {
+    DEBUG_WM(F("scanNetworks failed with unknown error code!"));
+  }
+  else if (n == 0)
+  {
+    DEBUG_WM(F("No networks found"));
+    // page += F("No networks found. Refresh to scan again.");
+  }
+  else
+  {
+    DEBUG_WM(F("Scan done"));
+  }
+
+  if (n > 0)
+  {
+    // WE SHOULD MOVE THIS IN PLACE ATOMICALLY
+    if (wifiSSIDs)
+    {
+      delete[] wifiSSIDs;
+    }
+    wifiSSIDs = new WiFiResult[n];
+    wifiSSIDCount = n;
+
+    if (n > 0)
+    {
+      shouldscan = false;
+    }
+    for (wifi_ssid_count_t i = 0; i < n; i++)
+    {
+      wifiSSIDs[i].duplicate = false;
+
+#if defined(ESP8266)
+      WiFi.getNetworkInfo(i,
+                          wifiSSIDs[i].SSID,
+                          wifiSSIDs[i].encryptionType,
+                          wifiSSIDs[i].RSSI,
+                          wifiSSIDs[i].BSSID,
+                          wifiSSIDs[i].channel,
+                          wifiSSIDs[i].isHidden);
+#else
+      WiFi.getNetworkInfo(i,
+                          wifiSSIDs[i].SSID,
+                          wifiSSIDs[i].encryptionType,
+                          wifiSSIDs[i].RSSI,
+                          wifiSSIDs[i].BSSID,
+                          wifiSSIDs[i].channel);
+#endif
+    }
+
+    // RSSI SORT
+
+    // old sort
+    for (int i = 0; i < n; i++)
+    {
+      for (int j = i + 1; j < n; j++)
+      {
+        if (wifiSSIDs[j].RSSI > wifiSSIDs[i].RSSI)
+        {
+          std::swap(wifiSSIDs[i], wifiSSIDs[j]);
+        }
+      }
+    }
+
+    // remove duplicates ( must be RSSI sorted )
+    if (_removeDuplicateAPs)
+    {
+      String cssid;
+      for (int i = 0; i < n; i++)
+      {
+        if (wifiSSIDs[i].duplicate == true)
+        {
+          continue;
+        }
+        cssid = wifiSSIDs[i].SSID;
+        for (int j = i + 1; j < n; j++)
+        {
+          if (cssid == wifiSSIDs[j].SSID)
+          {
+            DEBUG_WM("DUP AP: " + wifiSSIDs[j].SSID);
+            wifiSSIDs[j].duplicate = true; // set dup aps to NULL
+          }
+        }
+      }
+    }
+  }
+}
+
+void AsyncWiFiManager::startConfigPortalModeless(char const *apName, char const *apPassword)
+{
+  _modeless = true;
+  _apName = apName;
+  _apPassword = apPassword;
+
+  /*
+  AJS - do we want this?
+  */
+
+  // setup AP
+  WiFi.mode(WIFI_AP_STA);
+  DEBUG_WM(F("SET AP STA"));
+
+  // try to connect
+  if (connectWifi("", "") == WL_CONNECTED)
+  {
+    DEBUG_WM(F("IP Address:"));
+    DEBUG_WM(WiFi.localIP());
+    if (_connectcallback!=NULL){
+      _connectcallback();
+    }
+    // connected
+    // call the callback!
+    if (_savecallback != NULL)
+    {
+      // TODO: check if any custom parameters actually exist, and check if they really changed maybe
+      _savecallback();
+    }
+  }
+
+  // notify we entered AP mode
+  if (_apcallback != NULL)
+  {
+    _apcallback(this);
+  }
+
+  connect = false;
+  setupConfigPortal();
+  scannow = 0;
+}
+
+void AsyncWiFiManager::loop()
+{
+  safeLoop();
+  criticalLoop();
+}
+
+void AsyncWiFiManager::setInfo()
+{
+  if (needInfo)
+  {
+    pager = infoAsString();
+    wifiStatus = WiFi.status();
+    needInfo = false;
+  }
+}
+
+// anything that accesses WiFi, ESP or EEPROM goes here
+void AsyncWiFiManager::criticalLoop()
+{
+  if (_modeless)
+  {
+    if (scannow == 0 || millis() - scannow >= 60000)
+    {
+      scannow = millis();
+      scan(true);
+    }
+
+    wifi_ssid_count_t n = WiFi.scanComplete();
+    if (n >= 0)
+    {
+      copySSIDInfo(n);
+      WiFi.scanDelete();
+    }
+
+    if (connect)
+    {
+      connect = false;
+      //delay(2000);
+      DEBUG_WM(F("Connecting to new AP"));
+
+      // using user-provided _ssid, _pass in place of system-stored ssid and pass
+      if (connectWifi(_ssid, _pass) != WL_CONNECTED)
+      {
+        DEBUG_WM(F("Failed to connect"));
+      }
+      else
+      {
+        // connected
+        // alanswx - should we have a config to decide if we should shut down AP?
+        // WiFi.mode(WIFI_STA);
+        // notify that configuration has changed and any optional parameters should be saved
+        if (_savecallback != NULL)
+        {
+          // TODO: check if any custom parameters actually exist, and check if they really changed maybe
+          _savecallback();
+        }
+
+        return;
+      }
+
+      if (_shouldBreakAfterConfig)
+      {
+        // flag set to exit after config after trying to connect
+        // notify that configuration has changed and any optional parameters should be saved
+        if (_savecallback != NULL)
+        {
+          // TODO: check if any custom parameters actually exist, and check if they really changed maybe
+          _savecallback();
+        }
+      }
+    }
+  }
+}
+
+// anything that doesn't access WiFi, ESP or EEPROM can go here
+void AsyncWiFiManager::safeLoop()
+{
+#ifndef USE_EADNS
+  dnsServer->processNextRequest();
+#endif
+}
+
+boolean AsyncWiFiManager::startConfigPortal(char const *apName, char const *apPassword)
+{
+  // setup AP
+  WiFi.mode(WIFI_AP_STA);
+  DEBUG_WM(F("SET AP STA"));
+
+  _apName = apName;
+  _apPassword = apPassword;
+  bool connectedDuringConfigPortal = false;
+
+  // notify we entered AP mode
+  if (_apcallback != NULL)
+  {
+    _apcallback(this);
+  }
+
+  connect = false;
+  setupConfigPortal();
+  scannow = 0;
+  while (_configPortalTimeout == 0 || millis() - _configPortalStart < _configPortalTimeout)
+  {
+// DNS
+#ifndef USE_EADNS
+    dnsServer->processNextRequest();
+#endif
+    //
+    //  we should do a scan every so often here and
+    //  try to reconnect to AP while we are at it
+    //
+    if (scannow == 0 || millis() - scannow >= 10000)
+    {
+      DEBUG_WM(F("About to scan()"));
+      shouldscan = true; // since we are modal, we can scan every time
+#if defined(ESP8266)
+      // we might still be connecting, so that has to stop for scanning
+      ETS_UART_INTR_DISABLE();
+      wifi_station_disconnect();
+      ETS_UART_INTR_ENABLE();
+#else
+      WiFi.disconnect(false);
+#endif
+      scanModal();
+      if (_tryConnectDuringConfigPortal)
+      {
+        WiFi.begin(); // try to reconnect to AP
+        connectedDuringConfigPortal = true;
+      }
+      scannow = millis();
+    }
+
+    // attempts to reconnect were successful
+    if (WiFi.status() == WL_CONNECTED)
+    {
+      // connected
+      WiFi.mode(WIFI_STA);
+      // notify that configuration has changed and any optional parameters should be saved
+      // configuraton should not be saved when just connected using stored ssid and password during config portal
+      if (!connectedDuringConfigPortal && _savecallback != NULL)
+      {
+        // TODO: check if any custom parameters actually exist, and check if they really changed maybe
+        _savecallback();
+      }
+      break;
+    }
+
+    if (connect)
+    {
+      connect = false;
+      delay(2000);
+      DEBUG_WM(F("Connecting to new AP"));
+
+      // using user-provided _ssid, _pass in place of system-stored ssid and pass
+      if (_tryConnectDuringConfigPortal and connectWifi(_ssid, _pass) == WL_CONNECTED)
+      {
+        // connected
+        WiFi.mode(WIFI_STA);
+        // notify that configuration has changed and any optional parameters should be saved
+        if (_savecallback != NULL)
+        {
+          // TODO: check if any custom parameters actually exist, and check if they really changed maybe
+          _savecallback();
+        }
+        break;
+      }
+      else
+      {
+          if(_tryConnectDuringConfigPortal)
+            DEBUG_WM(F("Failed to connect"));
+      }
+
+      if (_shouldBreakAfterConfig)
+      {
+        // flag set to exit after config after trying to connect
+        // notify that configuration has changed and any optional parameters should be saved
+        if (_savecallback != NULL)
+        {
+          // TODO: check if any custom parameters actually exist, and check if they really changed maybe
+          _savecallback();
+        }
+        break;
+      }
+    }
+    yield();
+  }
+
+  server->reset();
+  dnsServer->stop();
+
+  return WiFi.status() == WL_CONNECTED;
+}
+
+uint8_t AsyncWiFiManager::connectWifi(String ssid, String pass)
+{
+  DEBUG_WM(F("Connecting as wifi client..."));
+
+  // check if we've got static_ip settings, if we do, use those
+  if (_sta_static_ip)
+  {
+    DEBUG_WM(F("Custom STA IP/GW/Subnet/DNS"));
+    WiFi.config(_sta_static_ip, _sta_static_gw, _sta_static_sn, _sta_static_dns1, _sta_static_dns2);
+    DEBUG_WM(WiFi.localIP());
+  }
+  // fix for auto connect racing issue
+  //  if (WiFi.status() == WL_CONNECTED) {
+  //    DEBUG_WM("Already connected. Bailing out.");
+  //    return WL_CONNECTED;
+  //  }
+  // check if we have ssid and pass and force those, if not, try with last saved values
+  if (ssid != "")
+  {
+#if defined(ESP8266)
+    // trying to fix connection in progress hanging
+    ETS_UART_INTR_DISABLE();
+    wifi_station_disconnect();
+    ETS_UART_INTR_ENABLE();
+#else
+    WiFi.disconnect(false);
+#endif
+    WiFi.begin(ssid.c_str(), pass.c_str());
+  }
+  else
+  {
+    if (WiFi.SSID().length() > 0)
+    {
+      DEBUG_WM(F("Using last saved values, should be faster"));
+#if defined(ESP8266)
+      // trying to fix connection in progress hanging
+      ETS_UART_INTR_DISABLE();
+      wifi_station_disconnect();
+      ETS_UART_INTR_ENABLE();
+#else
+      WiFi.disconnect(false);
+#endif
+      WiFi.begin();
+    }
+    else
+    {
+      DEBUG_WM(F("Try to connect with saved credentials"));
+      WiFi.begin();
+    }
+  }
+
+  uint8_t connRes = waitForConnectResult();
+  DEBUG_WM(F("Connection result: "));
+  DEBUG_WM(connRes);
+  if (WiFi.status() == WL_CONNECTED)
+  {
+
+      if (_connectcallback!=NULL){
+         _connectcallback();
+      }
+  }
+  // not connected, WPS enabled, no pass - first attempt
+#ifdef NO_EXTRA_4K_HEAP
+  if (_tryWPS && connRes != WL_CONNECTED && pass == "")
+  {
+    startWPS();
+    // should be connected at the end of WPS
+    connRes = waitForConnectResult();
+  }
+#endif
+  needInfo = true;
+  setInfo();
+  return connRes;
+}
+
+uint8_t AsyncWiFiManager::waitForConnectResult()
+{
+  if (_connectTimeout == 0)
+  {
+    return WiFi.waitForConnectResult();
+  }
+  else
+  {
+    DEBUG_WM(F("Waiting for connection result with time out"));
+    unsigned long start = millis();
+    boolean keepConnecting = true;
+    uint8_t status;
+    while (keepConnecting)
+    {
+      status = WiFi.status();
+      if (millis() > start + _connectTimeout)
+      {
+        keepConnecting = false;
+        DEBUG_WM(F("Connection timed out"));
+      }
+      if (status == WL_CONNECTED || status == WL_CONNECT_FAILED)
+      {
+        keepConnecting = false;
+      }
+      delay(100);
+    }
+    return status;
+  }
+}
+#ifdef NO_EXTRA_4K_HEAP
+void AsyncWiFiManager::startWPS()
+{
+  DEBUG_WM(F("START WPS"));
+#if defined(ESP8266)
+  WiFi.beginWPSConfig();
+#else
+  //esp_wps_config_t config = WPS_CONFIG_INIT_DEFAULT(ESP_WPS_MODE);
+  esp_wps_config_t config = {};
+  config.wps_type = ESP_WPS_MODE;
+  config.crypto_funcs = &g_wifi_default_wps_crypto_funcs;
+  strcpy(config.factory_info.manufacturer, "ESPRESSIF");
+  strcpy(config.factory_info.model_number, "ESP32");
+  strcpy(config.factory_info.model_name, "ESPRESSIF IOT");
+  strcpy(config.factory_info.device_name, "ESP STATION");
+
+  esp_wifi_wps_enable(&config);
+  esp_wifi_wps_start(0);
+#endif
+  DEBUG_WM(F("END WPS"));
+}
+#endif
+String AsyncWiFiManager::getConfigPortalSSID()
+{
+  return _apName;
+}
+
+void AsyncWiFiManager::resetSettings()
+{
+  DEBUG_WM(F("settings invalidated"));
+  DEBUG_WM(F("THIS MAY CAUSE AP NOT TO START UP PROPERLY. YOU NEED TO COMMENT IT OUT AFTER ERASING THE DATA."));
+
+  WiFi.mode(WIFI_AP_STA); // cannot erase if not in STA mode !
+  WiFi.persistent(true);
+#if defined(ESP8266)
+  WiFi.disconnect(true);
+#else
+  WiFi.disconnect(true, true);
+#endif
+  WiFi.persistent(false);
+
+  //delay(200);
+}
+void AsyncWiFiManager::setTimeout(unsigned long seconds)
+{
+  setConfigPortalTimeout(seconds);
+}
+
+void AsyncWiFiManager::setConfigPortalTimeout(unsigned long seconds)
+{
+  _configPortalTimeout = seconds * 1000;
+}
+
+void AsyncWiFiManager::setConnectTimeout(unsigned long seconds)
+{
+  _connectTimeout = seconds * 1000;
+}
+
+void AsyncWiFiManager::setTryConnectDuringConfigPortal(boolean v)
+{
+  _tryConnectDuringConfigPortal = v;
+}
+
+void AsyncWiFiManager::setDebugOutput(boolean debug)
+{
+  _debug = debug;
+}
+
+void AsyncWiFiManager::setAPStaticIPConfig(IPAddress ip,
+                                           IPAddress gw,
+                                           IPAddress sn)
+{
+  _ap_static_ip = ip;
+  _ap_static_gw = gw;
+  _ap_static_sn = sn;
+}
+
+void AsyncWiFiManager::setSTAStaticIPConfig(IPAddress ip,
+                                            IPAddress gw,
+                                            IPAddress sn,
+                                            IPAddress dns1,
+                                            IPAddress dns2)
+{
+  _sta_static_ip = ip;
+  _sta_static_gw = gw;
+  _sta_static_sn = sn;
+  _sta_static_dns1 = dns1;
+  _sta_static_dns2 = dns2;
+}
+
+void AsyncWiFiManager::setMinimumSignalQuality(unsigned int quality)
+{
+  _minimumQuality = quality;
+}
+
+void AsyncWiFiManager::setBreakAfterConfig(boolean shouldBreak)
+{
+  _shouldBreakAfterConfig = shouldBreak;
+}
+
+// handle root or redirect to captive portal
+void AsyncWiFiManager::handleRoot(AsyncWebServerRequest *request)
+{
+  // AJS - maybe we should set a scan when we get to the root???
+  // and only scan on demand? timer + on demand? plus a link to make it happen?
+
+  shouldscan = true;
+  scannow = 0;
+  DEBUG_WM(F("Handle root"));
+
+  if (captivePortal(request))
+  {
+    // if captive portal redirect instead of displaying the page
+    return;
+  }
+
+  DEBUG_WM(F("Sending Captive Portal"));
+
+  String page = FPSTR(WFM_HTTP_HEAD);
+  page.replace("{v}", "Options");
+  page += FPSTR(HTTP_SCRIPT);
+  page += FPSTR(HTTP_STYLE);
+  page += _customHeadElement;
+  page += FPSTR(HTTP_HEAD_END);
+  page += "<h1>";
+  page += _apName;
+  page += "</h1>";
+  page += F("<h3>AsyncWiFiManager</h3>");
+  page += FPSTR(HTTP_PORTAL_OPTIONS);
+  page += _customOptionsElement;
+  page += FPSTR(HTTP_END);
+
+  request->send(200, "text/html", page);
+  DEBUG_WM(F("Sent..."));
+}
+
+// wifi config page handler
+void AsyncWiFiManager::handleWifi(AsyncWebServerRequest *request, boolean scan)
+{
+  shouldscan = true;
+  scannow = 0;
+
+  DEBUG_WM(F("Handle wifi"));
+
+  String page = FPSTR(WFM_HTTP_HEAD);
+  page.replace("{v}", "Config ESP");
+  page += FPSTR(HTTP_SCRIPT);
+  page += FPSTR(HTTP_STYLE);
+  page += _customHeadElement;
+  page += FPSTR(HTTP_HEAD_END);
+
+  if (scan)
+  {
+    wifiSSIDscan = false;
+
+    DEBUG_WM(F("Scan done"));
+    if (wifiSSIDCount == 0)
+    {
+      DEBUG_WM(F("No networks found"));
+      page += F("No networks found. Refresh to scan again");
+    }
+    else
+    {
+      // display networks in page
+      String pager = networkListAsString();
+      page += pager;
+      page += "<br/>";
+    }
+  }
+  wifiSSIDscan = true;
+
+  page += FPSTR(HTTP_FORM_START);
+  char parLength[2];
+
+  // add the extra parameters to the form
+  for (unsigned int i = 0; i < _paramsCount; i++)
+  {
+    if (_params[i] == NULL)
+    {
+      break;
+    }
+
+    String pitem = FPSTR(HTTP_FORM_PARAM);
+    if (_params[i]->getID() != NULL)
+    {
+      pitem.replace("{i}", _params[i]->getID());
+      pitem.replace("{n}", _params[i]->getID());
+      pitem.replace("{p}", _params[i]->getPlaceholder());
+      snprintf(parLength, 2, "%d", _params[i]->getValueLength());
+      pitem.replace("{l}", parLength);
+      pitem.replace("{v}", _params[i]->getValue());
+      pitem.replace("{c}", _params[i]->getCustomHTML());
+    }
+    else
+    {
+      pitem = _params[i]->getCustomHTML();
+    }
+
+    page += pitem;
+  }
+  if (_params[0] != NULL)
+  {
+    page += "<br/>";
+  }
+  if (_sta_static_ip)
+  {
+    String item = FPSTR(HTTP_FORM_PARAM);
+    item.replace("{i}", "ip");
+    item.replace("{n}", "ip");
+    item.replace("{p}", "Static IP");
+    item.replace("{l}", "15");
+    item.replace("{v}", _sta_static_ip.toString());
+
+    page += item;
+
+    item = FPSTR(HTTP_FORM_PARAM);
+    item.replace("{i}", "gw");
+    item.replace("{n}", "gw");
+    item.replace("{p}", "Static Gateway");
+    item.replace("{l}", "15");
+    item.replace("{v}", _sta_static_gw.toString());
+
+    page += item;
+
+    item = FPSTR(HTTP_FORM_PARAM);
+    item.replace("{i}", "sn");
+    item.replace("{n}", "sn");
+    item.replace("{p}", "Subnet");
+    item.replace("{l}", "15");
+    item.replace("{v}", _sta_static_sn.toString());
+
+    page += item;
+
+    item = FPSTR(HTTP_FORM_PARAM);
+    item.replace("{i}", "dns1");
+    item.replace("{n}", "dns1");
+    item.replace("{p}", "DNS1");
+    item.replace("{l}", "15");
+    item.replace("{v}", _sta_static_dns1.toString());
+
+    page += item;
+
+    item = FPSTR(HTTP_FORM_PARAM);
+    item.replace("{i}", "dns2");
+    item.replace("{n}", "dns2");
+    item.replace("{p}", "DNS2");
+    item.replace("{l}", "15");
+    item.replace("{v}", _sta_static_dns2.toString());
+
+    page += item;
+    page += "<br/>";
+  }
+  page += FPSTR(HTTP_FORM_END);
+  page += FPSTR(HTTP_SCAN_LINK);
+  page += FPSTR(HTTP_END);
+
+  request->send(200, "text/html", page);
+
+  DEBUG_WM(F("Sent config page"));
+}
+
+// handle the WLAN save form and redirect to WLAN config page again
+void AsyncWiFiManager::handleWifiSave(AsyncWebServerRequest *request)
+{
+  DEBUG_WM(F("WiFi save"));
+
+  // SAVE/connect here
+  needInfo = true;
+  _ssid = request->arg("s").c_str();
+  _pass = request->arg("p").c_str();
+
+  // parameters
+  for (unsigned int i = 0; i < _paramsCount; i++)
+  {
+    if (_params[i] == NULL)
+    {
+      break;
+    }
+    // read parameter
+    String value = request->arg(_params[i]->getID()).c_str();
+    // store it in array
+    value.toCharArray(_params[i]->_value, _params[i]->_length);
+
+    DEBUG_WM(F("Parameter"));
+    DEBUG_WM(_params[i]->getID());
+    DEBUG_WM(value);
+  }
+
+  if (request->hasArg("ip"))
+  {
+    DEBUG_WM(F("static ip"));
+    DEBUG_WM(request->arg("ip"));
+    //_sta_static_ip.fromString(request->arg("ip"));
+    String ip = request->arg("ip");
+    optionalIPFromString(&_sta_static_ip, ip.c_str());
+  }
+  if (request->hasArg("gw"))
+  {
+    DEBUG_WM(F("static gateway"));
+    DEBUG_WM(request->arg("gw"));
+    String gw = request->arg("gw");
+    optionalIPFromString(&_sta_static_gw, gw.c_str());
+  }
+  if (request->hasArg("sn"))
+  {
+    DEBUG_WM(F("static netmask"));
+    DEBUG_WM(request->arg("sn"));
+    String sn = request->arg("sn");
+    optionalIPFromString(&_sta_static_sn, sn.c_str());
+  }
+  if (request->hasArg("dns1"))
+  {
+    DEBUG_WM(F("static DNS 1"));
+    DEBUG_WM(request->arg("dns1"));
+    String dns1 = request->arg("dns1");
+    optionalIPFromString(&_sta_static_dns1, dns1.c_str());
+  }
+  if (request->hasArg("dns2"))
+  {
+    DEBUG_WM(F("static DNS 2"));
+    DEBUG_WM(request->arg("dns2"));
+    String dns2 = request->arg("dns2");
+    optionalIPFromString(&_sta_static_dns2, dns2.c_str());
+  }
+
+  String page = FPSTR(WFM_HTTP_HEAD);
+  page.replace("{v}", "Credentials Saved");
+  page += FPSTR(HTTP_SCRIPT);
+  page += FPSTR(HTTP_STYLE);
+  page += _customHeadElement;
+  page += F("<meta http-equiv=\"refresh\" content=\"5; url=/i\">");
+  page += FPSTR(HTTP_HEAD_END);
+  page += FPSTR(HTTP_SAVED);
+  page += FPSTR(HTTP_END);
+
+  request->send(200, "text/html", page);
+
+  DEBUG_WM(F("Sent wifi save page"));
+
+  connect = true; // signal ready to connect/reset
+}
+
+// handle the info page
+String AsyncWiFiManager::infoAsString()
+{
+  String page;
+  page += F("<dt>Chip ID</dt><dd>");
+#if defined(ESP8266)
+  page += ESP.getChipId();
+#else
+  page += getESP32ChipID();
+#endif
+  page += F("</dd>");
+  page += F("<dt>Flash Chip ID</dt><dd>");
+#if defined(ESP8266)
+  page += ESP.getFlashChipId();
+#else
+  page += F("N/A for ESP32");
+#endif
+  page += F("</dd>");
+  page += F("<dt>IDE Flash Size</dt><dd>");
+  page += ESP.getFlashChipSize();
+  page += F(" bytes</dd>");
+  page += F("<dt>Real Flash Size</dt><dd>");
+#if defined(ESP8266)
+  page += ESP.getFlashChipRealSize();
+#else
+  page += F("N/A for ESP32");
+#endif
+  page += F(" bytes</dd>");
+  page += F("<dt>Soft AP IP</dt><dd>");
+  page += WiFi.softAPIP().toString();
+  page += F("</dd>");
+  page += F("<dt>Soft AP MAC</dt><dd>");
+  page += WiFi.softAPmacAddress();
+  page += F("</dd>");
+  page += F("<dt>Station SSID</dt><dd>");
+  page += WiFi.SSID();
+  page += F("</dd>");
+  page += F("<dt>Station IP</dt><dd>");
+  page += WiFi.localIP().toString();
+  page += F("</dd>");
+  page += F("<dt>Station MAC</dt><dd>");
+  page += WiFi.macAddress();
+  page += F("</dd>");
+  page += F("</dl>");
+  return page;
+}
+
+void AsyncWiFiManager::handleInfo(AsyncWebServerRequest *request)
+{
+  DEBUG_WM(F("Info"));
+
+  String page = FPSTR(WFM_HTTP_HEAD);
+  page.replace("{v}", "Info");
+  page += FPSTR(HTTP_SCRIPT);
+  page += FPSTR(HTTP_STYLE);
+  page += _customHeadElement;
+  if (connect == true)
+  {
+    page += F("<meta http-equiv=\"refresh\" content=\"5; url=/i\">");
+  }
+  page += FPSTR(HTTP_HEAD_END);
+  page += F("<dl>");
+  if (connect == true)
+  {
+    page += F("<dt>Trying to connect</dt><dd>");
+    page += wifiStatus;
+    page += F("</dd>");
+  }
+  page += pager;
+  page += FPSTR(HTTP_END);
+
+  request->send(200, "text/html", page);
+
+  DEBUG_WM(F("Sent info page"));
+}
+
+// handle the reset page
+void AsyncWiFiManager::handleReset(AsyncWebServerRequest *request)
+{
+  DEBUG_WM(F("Reset"));
+
+  String page = FPSTR(WFM_HTTP_HEAD);
+  page.replace("{v}", "Info");
+  page += FPSTR(HTTP_SCRIPT);
+  page += FPSTR(HTTP_STYLE);
+  page += _customHeadElement;
+  page += FPSTR(HTTP_HEAD_END);
+  page += F("Module will reset in a few seconds");
+  page += FPSTR(HTTP_END);
+  request->send(200, "text/html", page);
+
+  DEBUG_WM(F("Sent reset page"));
+  delay(5000);
+#if defined(ESP8266)
+  ESP.reset();
+#else
+  ESP.restart();
+#endif
+  delay(2000);
+}
+
+void AsyncWiFiManager::handleNotFound(AsyncWebServerRequest *request)
+{
+  DEBUG_WM(F("Handle not found"));
+  if (captivePortal(request))
+  {
+    // if captive portal redirect instead of displaying the error page
+    return;
+  }
+
+  String message = "File Not Found\n\n";
+  message += "URI: ";
+  message += request->url();
+  message += "\nMethod: ";
+  message += (request->method() == HTTP_GET) ? "GET" : "POST";
+  message += "\nArguments: ";
+  message += request->args();
+  message += "\n";
+
+  for (unsigned int i = 0; i < request->args(); i++)
+  {
+    message += " " + request->argName(i) + ": " + request->arg(i) + "\n";
+  }
+  AsyncWebServerResponse *response = request->beginResponse(404, "text/plain", message);
+  response->addHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+  response->addHeader("Pragma", "no-cache");
+  response->addHeader("Expires", "-1");
+  request->send(response);
+}
+
+/** Redirect to captive portal if we got a request for another domain.
+ * Return true in that case so the page handler do not try to handle the request again. */
+boolean AsyncWiFiManager::captivePortal(AsyncWebServerRequest *request)
+{
+  if (!isIp(request->host()))
+  {
+    DEBUG_WM(F("Request redirected to captive portal"));
+    AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "");
+    response->addHeader("Location", String("http://") + toStringIp(request->client()->localIP()));
+    request->send(response);
+    return true;
+  }
+  return false;
+}
+
+// start up config portal callback
+void AsyncWiFiManager::setAPCallback(std::function<void(AsyncWiFiManager *)> func)
+{
+  _apcallback = func;
+}
+
+// start up save config callback
+void AsyncWiFiManager::setSaveConfigCallback(std::function<void()> func)
+{
+  _savecallback = func;
+}
+void AsyncWiFiManager::setConnectCallback(std::function<void()> func)
+{
+  _connectcallback = func;
+}
+
+// sets a custom element to add to head, like a new style tag
+void AsyncWiFiManager::setCustomHeadElement(const char *element)
+{
+  _customHeadElement = element;
+}
+
+// sets a custom element to add to options page
+void AsyncWiFiManager::setCustomOptionsElement(const char *element)
+{
+  _customOptionsElement = element;
+}
+
+// if this is true, remove duplicated Access Points - defaut true
+void AsyncWiFiManager::setRemoveDuplicateAPs(boolean removeDuplicates)
+{
+  _removeDuplicateAPs = removeDuplicates;
+}
+
+template <typename Generic>
+void AsyncWiFiManager::DEBUG_WM(Generic text)
+{
+  if (_debug)
+  {
+    Serial.print(F("*WM: "));
+    Serial.println(text);
+  }
+}
+
+unsigned int AsyncWiFiManager::getRSSIasQuality(int RSSI)
+{
+  unsigned int quality = 0;
+
+  if (RSSI <= -100)
+  {
+    quality = 0;
+  }
+  else if (RSSI >= -50)
+  {
+    quality = 100;
+  }
+  else
+  {
+    quality = 2 * (RSSI + 100);
+  }
+  return quality;
+}
+
+// is this an IP?
+boolean AsyncWiFiManager::isIp(String str)
+{
+  for (unsigned int i = 0; i < str.length(); i++)
+  {
+    int c = str.charAt(i);
+    if (c != '.' && (c < '0' || c > '9'))
+    {
+      return false;
+    }
+  }
+  return true;
+}
+
+// IP to String?
+String AsyncWiFiManager::toStringIp(IPAddress ip)
+{
+  String res = "";
+  for (int i = 0; i < 3; i++)
+  {
+    res += String((ip >> (8 * i)) & 0xFF) + ".";
+  }
+  res += String(((ip >> 8 * 3)) & 0xFF);
+  return res;
+}

+ 15 - 0
sw/key800/src/abc800_callback.cpp

@@ -0,0 +1,15 @@
+#include <Arduino.h>
+#include <WiFi.h>
+#include "abc800_callback.h"
+#include "abc800_web.h"
+
+static const char TAG[] = __FILE__;
+extern abc800::web abc800_web;
+namespace abc800 {
+
+	void callback::wificonnect(){
+		ESP_LOGI(TAG,"WiFi Connected start web");
+		abc800_web.init();
+
+	}
+}

+ 159 - 0
sw/key800/src/abc800_func.cpp

@@ -0,0 +1,159 @@
+#include "config.h"
+
+#include "abc800_func.h"
+
+
+namespace abc800 {
+
+	void std_abc800::getmac_b(String mac, uint8_t *mac_b)
+	{
+		char firstchar;
+		char secchar;
+		uint8_t position = 0;
+
+		for (int i = 0; i < 6; i++)
+		{
+			firstchar = mac.charAt(position);
+			position++;
+			secchar = mac.charAt(position);
+			mac_b[i] = hex_to_ascii(firstchar, secchar);
+			position++;
+		}
+	}
+
+	int std_abc800::hex_to_int(char c) {
+		int first = c / 16 - 3;
+		int second = c % 16;
+		int result = first * 10 + second;
+		if (result > 9) result--;
+		return result;
+	}
+
+	int std_abc800::hex_to_ascii(char c, char d) {
+		int high = hex_to_int(c) * 16;
+		int low = hex_to_int(d);
+		return high + low;
+	}
+	void std_abc800::reverse8(uint8_t *buf, int size, uint8_t *rev)
+	{
+		for (int i = 0; i < size; i++) {
+			rev[size - 1 - i] = buf[i];
+		}
+	}
+	void std_abc800::hex2bin(uint8_t *out, const char *in, size_t *size)
+	{
+		size_t sz = 0;
+		while (*in) {
+			while (*in == ' ') in++;  // skip spaces
+			while (*in == ',') in++;  // skip spaces
+			while (*in == ' ') in++;  // skip spaces
+			if (!*in) break;
+			uint8_t c = *in>='a' ? *in-'a'+10 : *in>='A' ? *in-'A'+10 : *in-'0';
+			in++;
+			c <<= 4;
+			if (!*in) break;
+			c |= *in>='a' ? *in-'a'+10 : *in>='A' ? *in-'A'+10 : *in-'0';
+			in++;
+			*out++ = c;
+			sz++;
+		}
+		//size = sz;
+	}
+	String std_abc800::hexaddr(uint8_t *buf) {
+
+		String output = "";
+		for (uint8_t i = 0; i < 8; i++)
+		{
+			output += "0x";
+			if (buf[i] < 0x10) output += "0";
+			output += String(buf[i], HEX);
+			if (i < 7) output += (", ");
+		}
+		return output;
+	}
+
+	void std_abc800::serial_empty(HardwareSerial *port) {
+		HardwareSerial * _port;
+		_port = port;
+		_port->read();
+		_port->read();
+		_port->read();
+		_port->read();
+		_port->flush();
+	}
+
+	String std_abc800::hex_to_string(uint8_t *buf, int size, String separator) {
+
+		String output = "";
+		for (int i = 0; i < size; i++) {
+			if (buf[i] < 0x10)
+				output += "0";
+			output += String(buf[i], HEX);
+			if (separator != "") {
+				output += separator;
+			}
+		}
+		return output;
+	}
+	void std_abc800::processutf8(uint8_t b,utf8decodedata *data)
+	{
+		if (data->state == 0x01) {
+			// Expect a continuation byte
+			if ((b & 0xc0) == 0x80) {
+			// Continuation byte
+			data->acc <<= 6;
+			data->acc |= (uint16_t) (b & 0x3f);
+			data->left--;
+			if (data->left == 0) {
+				// Last continuation byte in sequence
+				data->data=data->acc;
+				data->state = 0x00;
+				return;
+			}
+			} else {
+			// Invalid byte; retry from here in INIT state
+			data->error = 0x01;
+			data->state = 0x00;
+			}
+		}
+		if (data->state == 0x00) {
+			// Expect an initial byte
+			if ((b & 0x80) == 0x00) {
+			// Ascii byte
+			data->data=((uint16_t) b);
+			} else if ((b & 0xe0) == 0xc0) {
+			// Start of two-byte sequence
+			data->state = 0x01;
+			data->acc = (uint16_t) (b & 0x1f);
+			data->left = 1;
+			} else if ((b & 0xf0) == 0xe0) {
+			// Start of three-byte sequence
+			data->state = 0x01;
+			data->acc = (uint16_t) (b & 0x0f);
+			data->left = 2;
+			} else {
+			// Too large sequence for this implementation or invalid byte
+			data->error;
+			}
+		}
+	}
+
+	uint8_t std_abc800::processutf8String(const uint8_t* s,uint8_t len,uint8_t* d)
+	{
+		uint8_t p;
+		utf8decodedata data;
+		data.state=0x00;
+		uint8_t i=0;
+		for (p = 0; p < len; p++) {
+			processutf8(s[p],&data);
+			if (data.data !=0x00){
+				d[i]=data.data;
+				data.data = 0x00;
+				i++;
+			}
+		}
+		d[i]=0x00;
+		return (i-1);
+	}
+
+}

+ 70 - 0
sw/key800/src/abc800_gpio.cpp

@@ -0,0 +1,70 @@
+#include <Arduino.h>
+#include "abc800_gpio.h"
+#include "abc800_keyboard.h"
+static const char TAG[] = __FILE__;
+extern portMUX_TYPE sync_isr;
+extern TaskHandle_t Keyboard_ABC800_Task_In;
+
+namespace abc800 {
+    void IRAM_ATTR abc800keybord_rst_isr(){
+        portENTER_CRITICAL(&sync_isr);
+        if (!xTaskNotifyFromISR(Keyboard_ABC800_Task_In, ABC800_KEY_IN_ABC800, eSetValueWithoutOverwrite,NULL))
+        {
+            ESP_LOGE(TAG,"Could not notify task");
+        }
+        portEXIT_CRITICAL(&sync_isr);
+    } 
+
+	void gpio::init(){
+        uint8_t i;
+        pinMode(KEY800_LED,OUTPUT);
+        ledcSetup(KEY800_PWM_CHANNEL, KEY800_PWM_FREQ, KEY800_PWM_RES);
+        pinMode(K800_RST ,INPUT);
+        pinMode(A800_RST ,OUTPUT);
+        digitalWrite(A800_RST,LOW);
+        pinMode(K800_TRXC ,INPUT);
+        pinMode(A800_TRXC ,OUTPUT);
+        pinMode(K800_KD ,INPUT);
+        pinMode(A800_KD ,OUTPUT);
+        //Attach PWM
+        ledcAttachPin(A800_TRXC, KEY800_PWM_CHANNEL);
+        ledcWrite(KEY800_PWM_CHANNEL,KEY800_PWM_DUTY);
+        
+        //Serial2.begin(650,SERIAL_8N2,A800_RXD,A800_TXD);
+        attachInterrupt(K800_RST,abc800keybord_rst_isr,CHANGE);
+
+    }
+    uint8_t gpio::sendkey(uint8_t key){
+        if (key!=0xff){
+            ESP_LOGD(TAG,"OUT CODE %02x",key);
+            digitalWrite(A800_KD, HIGH);
+            Serial1.write(key);
+            vTaskDelay( ABC800_KEY_DELAY_LEGACY);  // Enter needs longer delay so basic has enuff time to parse
+            digitalWrite(A800_KD, LOW);
+        }
+        return 0;
+    }
+    uint8_t gpio::abc800_getupdown(){
+        return (digitalRead(K800_KD));
+    }
+    uint8_t gpio::abc800_getkey(){
+        uint8_t key=0;
+        /*
+        key =  digitalRead(A80IN_3);
+        key = key << 1;
+        key = key | digitalRead(A80IN_2);
+        key = key << 1;
+        key = key | digitalRead(A80IN_1);
+        key = key << 1;
+        key = key | digitalRead(A80IN_7);
+        key = key << 1;
+        key = key | digitalRead(A80IN_6);
+        key = key << 1;
+        key = key | digitalRead(A80IN_5);
+        key = key << 1;
+        key = key | digitalRead(A80IN_4);
+        */
+        return key;
+    }
+
+}

+ 239 - 0
sw/key800/src/abc800_hidboot.cpp

@@ -0,0 +1,239 @@
+/* Copyright (C) 2011 Circuits At Home, LTD. All rights reserved.
+
+This software may be distributed and modified under the terms of the GNU
+General Public License version 2 (GPL2) as published by the Free Software
+Foundation and appearing in the file GPL2.TXT included in the packaging of
+this file. Please note that GPL2 Section 2[b] requires that all works based
+on this software must also be made publicly available under the terms of
+the GPL2 ("Copyleft").
+
+Contact information
+-------------------
+
+Circuits At Home, LTD
+Web      :  http://www.circuitsathome.com
+e-mail   :  support@circuitsathome.com
+*/
+#include "Arduino.h"
+#include "abc800_hidboot.h"
+#include "keyboardpipe.h"
+#include "esp_log.h"
+static const char TAG[] = __FILE__;
+
+
+/**
+ * \brief Parse HID keyboard report.
+ *
+ * \param hid HID device pointer.
+ * \param is_rpt_id True if this is a report ID.
+ * \param len Buffer length.
+ * \param buf Buffer containing report data.
+ */
+void KeyboardReportParser::Parse( uint32_t len, uint8_t *buf,USBHostKeyboard *port)
+{
+	// On error - return
+	if (buf[2] == 1)
+		return;
+
+	//KBDINFO	*pki = (KBDINFO*)buf;
+
+	for (uint32_t i = 2; i < 8; ++i)
+	{
+		bool down = false;
+		bool up	  = false;
+
+		for (uint8_t j=2; j<8; j++)
+		{
+			if (buf[i] == prevState.bInfo[j] && buf[i] != 1)
+				down = true;
+			if (buf[j] == prevState.bInfo[i] && prevState.bInfo[i] != 1)
+				up = true;
+		}
+
+		if (!down)
+		{
+			HandleLockingKeys( buf[i],port);
+			OnKeyDown(*buf, buf[i]);
+		}
+
+		if (!up)
+			OnKeyUp(prevState.bInfo[0], prevState.bInfo[i]);
+	}
+
+	for (uint32_t i = 0; i < 8; ++i)
+		prevState.bInfo[i] = buf[i];
+};
+
+/**
+ * \brief Handle keyboard locking keys and manage keyboard LEDs using USB
+ * report.
+ *
+ * \param hid HID device pointer.
+ * \param key Locking key.
+ *
+ * \return 0 on success, error code otherwise.
+ */
+uint8_t KeyboardReportParser::HandleLockingKeys( uint8_t key,USBHostKeyboard *port)
+{
+	uint8_t old_keys = kbdLockingKeys.bLeds;
+
+	switch (key)
+	{
+		case KEY_NUM_LOCK:
+			kbdLockingKeys.kbdLeds.bmNumLock = ~kbdLockingKeys.kbdLeds.bmNumLock;
+			break;
+		case KEY_CAPS_LOCK:
+			kbdLockingKeys.kbdLeds.bmCapsLock = ~kbdLockingKeys.kbdLeds.bmCapsLock;
+			break;
+		case KEY_SCROLL_LOCK:
+			kbdLockingKeys.kbdLeds.bmScrollLock = ~kbdLockingKeys.kbdLeds.bmScrollLock;
+			break;
+	}
+
+	if (old_keys != kbdLockingKeys.bLeds)
+		
+		ESP_LOGI(TAG,"%02x",kbdLockingKeys.bLeds);
+		port->sendData(&kbdLockingKeys.bLeds,1);
+		return 0;
+		
+
+	return 0;
+}
+/* English keyboard
+const uint8_t KeyboardReportParser::numKeys[] = { '!', '@', '#', '$', '%', '^', '&', '*', '(', ')' };
+const uint8_t KeyboardReportParser::symKeysUp[] = { '_', '+', '{', '}', '|', '~', ':', '"', '~', '<', '>', '?' };
+const uint8_t KeyboardReportParser::symKeysLo[] = { '-', '=', '[', ']', '\\', ' ', ';', '\'', '`', ',', '.', '/' };
+const uint8_t KeyboardReportParser::padKeys[] = { '/', '*', '-', '+', 0x13 };
+*/
+/* Swedish keyboard */
+const uint8_t KeyboardReportParser::numKeys[] = { '!', '"', '#', '¤', '%', '&', '/', '(', ')', '=' };
+const uint8_t KeyboardReportParser::symKeysUp[] = { 0x3f, '`', 0xC5, '^', '>', '*', 0xD6, 0xC4, '*', ';', ':', 0x5f };
+const uint8_t KeyboardReportParser::symKeysLo[] = { 0x2b, '=', 0xE5, '¨', '<', '\'', 0xF6, 0xE4, '\'', ',', '.', 0x2d };
+const uint8_t KeyboardReportParser::padKeys[] = { '/', '*', '-', '+', 0x13 };
+/**
+ * \brief Manage keyboard OEM to ASCII conversion.
+ *
+ * \param mod Keyboard modifier.
+ * \param key Key value to convert.
+ *
+ * \return Keyboard corresponding ASCII value on success, 0 otherwise.
+ */
+uint8_t KeyboardReportParser::OemToAscii(uint8_t mod, uint8_t key)
+{
+	uint8_t shift = (mod & 0x22);
+
+	// [a-z]
+	if (key > 0x03 && key < 0x1e)
+	{
+		// Upper case letters
+		if ((kbdLockingKeys.kbdLeds.bmCapsLock == 0 && shift) ||
+			 (kbdLockingKeys.kbdLeds.bmCapsLock == 1 && (shift == 0) ))
+			return (key - 4 + 'A');
+
+		// Lower case letters
+		else
+			return (key - 4 + 'a');
+	}
+	// Numbers
+	else if (key > 0x1d && key < 0x27)
+	{
+		if (shift)
+			return (numKeys[key - 0x1e]);
+		else
+			return (key - 0x1e + '1');
+	}
+	// Keypad Numbers
+	else if (key > 0x58 && key < 0x62)
+	{
+		if (kbdLockingKeys.kbdLeds.bmNumLock == 1)
+			return (key - 0x59 + '1');
+	}
+	else if (key == 0x33 ||  key ==0x34 || key == 0x2f) // Å Ä Ö
+	{
+		return (((kbdLockingKeys.kbdLeds.bmCapsLock == 0 && shift) || (kbdLockingKeys.kbdLeds.bmCapsLock == 1 && (shift == 0) ) ? symKeysUp[key-0x2d] : symKeysLo[key-0x2d]) );
+	}
+	else if (key == 0x30 ) // Û
+	{
+		return ((kbdLockingKeys.kbdLeds.bmCapsLock == 0 && shift) || (kbdLockingKeys.kbdLeds.bmCapsLock == 1 && (shift == 0) ) ? 0xFC : 0xDC );
+	}
+	else if (key == 0x2e ) // É
+	{
+		return ((kbdLockingKeys.kbdLeds.bmCapsLock == 0 && shift) || (kbdLockingKeys.kbdLeds.bmCapsLock == 1 && (shift == 0) ) ? 0xE9 : 0xC9 );
+	}
+	else if (key > 0x2c && key < 0x39){
+		return ((shift) ? symKeysUp[key-0x2d] : symKeysLo[key-0x2d]);
+	}
+	else if (key > 0x53 && key < 0x59)
+		return padKeys[key - 0x54];
+	else
+	{
+		switch (key)
+		{
+			case KEY_SPACE:		return (0x20);
+			case KEY_ENTER:		return (0x13);
+			case KEY_ZERO:		return ((shift) ? '=' : '0');
+			case KEY_ZERO2:		return ((kbdLockingKeys.kbdLeds.bmNumLock == 1) ? '0' : 0);
+			case KEY_PERIOD:	return ((kbdLockingKeys.kbdLeds.bmNumLock == 1) ? '.' : 0);
+			case KEY_LESSMORE:	return ((shift) ? '>' : '<');
+		}
+	}
+
+	return 0x00;
+}
+/**
+ * \brief Manage keyboard OEM to ABC80 conversion.
+ *
+ * \param mod Keyboard modifier.
+ * \param key Key value to convert.
+ *
+ * \return Keyboard corresponding ABC80 value on success, 0 otherwise.
+ */
+uint8_t KeyboardReportParser::OemToABC80(uint8_t mod, uint8_t key)
+{
+	uint8_t shift = (mod & 0x22);
+	uint8_t ctrl = (mod & 0x22);
+
+	// [a-z]
+	if (key > 0x03 && key < 0x1e)
+	{
+		// Upper case letters
+		if ( (kbdLockingKeys.kbdLeds.bmCapsLock == 0 && (mod & 2)) ||
+			 (kbdLockingKeys.kbdLeds.bmCapsLock == 1 && (mod & 2) == 0) )
+			return (key - 4 + 'A');
+
+		// Lower case letters
+		else
+			return (key - 4 + 'a');
+	}
+	// Numbers
+	else if (key > 0x1d && key < 0x27)
+	{
+		if (shift)
+			return (numKeys[key - 0x1e]);
+		else
+			return (key - 0x1e + '1');
+	}
+	// Keypad Numbers
+	else if (key > 0x58 && key < 0x62)
+	{
+		if (kbdLockingKeys.kbdLeds.bmNumLock == 1)
+			return (key - 0x59 + '1');
+	}
+	else if (key > 0x2c && key < 0x39)
+		return ((shift) ? symKeysUp[key-0x2d] : symKeysLo[key-0x2d]);
+	else if (key > 0x53 && key < 0x59)
+		return padKeys[key - 0x54];
+	else
+	{
+		switch (key)
+		{
+			case KEY_SPACE:		return (0x20);
+			case KEY_ENTER:		return (0x13);
+			case KEY_ZERO:		return ((shift) ? ')' : '0');
+			case KEY_ZERO2:		return ((kbdLockingKeys.kbdLeds.bmNumLock == 1) ? '0' : 0);
+			case KEY_PERIOD:	return ((kbdLockingKeys.kbdLeds.bmNumLock == 1) ? '.' : 0);
+		}
+	}
+
+	return 0;
+}

+ 512 - 0
sw/key800/src/abc800_keyboard.cpp

@@ -0,0 +1,512 @@
+
+#include <Arduino.h>
+#include "abc800_keyboard.h"
+#include "abc800_gpio.h"
+#include "abc800_config.h"
+#include "WiFi.h"
+static const char TAG[] = __FILE__;
+extern QueueHandle_t abc800_key_queue_out;
+extern QueueHandle_t abc800_key_queue_in;
+extern TaskHandle_t Keyboard_Task_Out;
+extern TaskHandle_t Keyboard_ABC800_Task_In;
+extern abc800::gpio abc800_gpio;
+extern abc800::keyboard abc800_keyboard;
+extern TimerHandle_t abc800_repeat_delay_timer;
+extern TimerHandle_t abc800_repeat_timer;
+extern TimerHandle_t abc800_check_timer;
+
+void  onRepeatTimer_abc800_in(TimerHandle_t xTimer) {
+  abc800_keyboard.repeat_abc800_key();
+  xTimerStart(abc800_repeat_timer, portMAX_DELAY);
+}
+void  onCheckTimer_abc800_in(TimerHandle_t xTimer) {
+  abc800_keyboard.check_abc800_key();
+}
+
+
+
+namespace abc800 
+{
+   	void KeyboardTaskOut(void * parameter)
+    {
+        key800queue key;
+        while (1) 
+        {
+            // See if there's a message in the queue (do not block)        
+            if (xQueueReceive(abc800_key_queue_out, (void *)&key, portMAX_DELAY) == pdTRUE) {
+                uint8_t special_key;
+                if (special_key=abc800_keyboard.specialABC800Keycode(key)){
+                    abc800_gpio.sendkey(special_key);
+                }else{
+                    abc800_gpio.sendkey(abc800_keyboard.getABC800Keycode(key)); 
+                }
+                vTaskDelay((key.ascii == 0x13 ? 100 : 4) / portTICK_PERIOD_MS);
+            }else{
+                vTaskDelay(1 / portTICK_PERIOD_MS);
+            }
+        }
+    }
+
+    void KeyboardABC800TaskIn(void * parameter)
+    {
+        key800queue key;
+        while (1) 
+        {
+            // See if there's a message in the queue (do not block) 
+            uint32_t keyboard_action = 0;
+            if (xTaskNotifyWait(0, ULONG_MAX, &keyboard_action , 5)  == pdPASS) {
+
+                if (abc800_gpio.abc800_getupdown()){
+                    uint8_t key = abc800_gpio.abc800_getkey();
+                    key800queue ascii = abc800_keyboard.getKeyboardKeycode(key);
+                    abc800_keyboard.abc800_old_key = key;
+                    if (xQueueSend(abc800_key_queue_out, (void *)&ascii, 0) != pdTRUE) {
+                        ESP_LOGE(TAG,"Queue full");
+                    }
+                    xTimerStop(abc800_repeat_delay_timer, portMAX_DELAY);
+                    xTimerStop(abc800_repeat_timer, portMAX_DELAY);
+                    xTimerStop(abc800_check_timer, portMAX_DELAY);
+                    xTimerStart(abc800_repeat_delay_timer, portMAX_DELAY);
+                    xTimerStart(abc800_check_timer, portMAX_DELAY);
+                }else{
+                    xTimerStop(abc800_repeat_delay_timer, portMAX_DELAY);
+                    xTimerStop(abc800_repeat_timer, portMAX_DELAY);
+                    xTimerStop(abc800_check_timer, portMAX_DELAY);
+                }
+
+            }       
+        }
+    }
+    
+    uint8_t keyboard::repeat_abc800_key (){
+        uint8_t key = abc800_gpio.abc800_getkey();
+        key800queue key800 = abc800_keyboard.getKeyboardKeycode(key);
+        if (xQueueSend(abc800_key_queue_out, (void *)&key800, 0) != pdTRUE) {
+            ESP_LOGE(TAG,"Queue full");
+        }
+        return 0;
+    }
+    uint8_t keyboard::check_abc800_key (){
+        uint8_t key = abc800_gpio.abc800_getkey();
+        if (key!=abc800_keyboard.abc800_old_key && abc800_gpio.abc800_getupdown()){
+            key800queue key800 = abc800_keyboard.getKeyboardKeycode(key);
+            if (xQueueSend(abc800_key_queue_out, (void *)&key800, 0) != pdTRUE) {
+                ESP_LOGE(TAG,"Queue full");
+            }
+            abc800_keyboard.abc800_old_key=key;
+        }
+        return 0;
+    }
+    void keyboard::init()
+    {
+        abc800_repeat_delay_timer = xTimerCreate(
+                      "ABC800 Repeat Delay Timer",           // Name of timer
+                      ABC800_KEY_REPEAT_TIME_DELAY/ portTICK_PERIOD_MS,  // Period of timer (in ticks)
+                      pdFALSE,                    // Auto-reload
+                      (void *)2,                  // Timer ID
+                      onRepeatTimer_abc800_in);           // Callback function
+
+        abc800_repeat_timer = xTimerCreate(
+                      "ABC800 Repeat Timer",        // Name of timer
+                      ABC800_KEY_REPEAT_TIME / portTICK_PERIOD_MS,  // Period of timer (in ticks)
+                      pdFALSE,                     // Auto-reload
+                      (void *)3,                  // Timer ID
+                      onRepeatTimer_abc800_in);    // Callback function
+        abc800_check_timer = xTimerCreate(
+                      "ABC800 Check Timer",        // Name of timer
+                      ABC800_KEY_CHECK_TIME / portTICK_PERIOD_MS,  // Period of timer (in ticks)
+                      pdFALSE,                     // Auto-reload
+                      (void *)4,                  // Timer ID
+                      onCheckTimer_abc800_in);    // Callback function
+
+       
+        xTaskCreatePinnedToCore(
+        KeyboardTaskOut,
+        "Keyboard_Task_Out",
+        2048,
+        NULL,
+        4,
+        &Keyboard_Task_Out,
+        1);
+
+        xTaskCreatePinnedToCore(
+        KeyboardABC800TaskIn,
+        "Keyboard_Task_ABC800_In",
+        2048,
+        NULL,
+        2,
+        &Keyboard_ABC800_Task_In,
+        1);
+
+    }
+    uint8_t keyboard::specialABC800Keycode(key800queue keyqueue){
+        // Arrow up
+        if (keyqueue.special == 0x52)
+        {
+            return (0x17);
+        }
+        // Arrow down
+        if (keyqueue.special == 0x51)
+        {
+            return (0x1a);
+        }
+        // Forward
+        if (keyqueue.special == 0x4f){
+            return(0x09);
+        }
+        // Backwards
+        if ( keyqueue.special == 0x50 ){
+            return(0x08);
+        }
+        if (keyqueue.special == 0x3a)
+        {
+            char x[15]={"10 print \"HEJ\""}; //F1
+            key800queue key800queue ;
+            key800queue.modifier=0x00;
+            key800queue.special=0x00;
+
+            for (uint8_t i=0;i<15;i++){
+                key800queue.ascii = x[i];
+                if (xQueueSend(abc800_key_queue_out, (void *)&key800queue, 0) != pdTRUE) {
+                    ESP_LOGE(TAG,"Queue full");
+                }
+            }
+            return (0x00);
+        }
+        if (keyqueue.special == 0x45) //F12
+        {
+            char * x= new char[50];
+            String IP ="rem IP-Address: "+WiFi.localIP().toString();
+            uint8_t len = IP.length();
+            
+            IP.toCharArray(x, len+1);
+            key800queue key800queue ;
+            key800queue.modifier=0x00;
+            key800queue.special=0x00;
+            for (uint8_t i=0;i<=(len);i++){
+                key800queue.ascii = x[i];
+                if (xQueueSend(abc800_key_queue_out, (void *)&key800queue, 0) != pdTRUE) {
+                    ESP_LOGE(TAG,"Queue full");
+                }
+            }
+            key800queue.ascii=0x13;
+            key800queue.modifier=0x00;
+            key800queue.special=0x00;
+            if (xQueueSend(abc800_key_queue_out, (void *)&key800queue, 0) != pdTRUE) {
+                    ESP_LOGE(TAG,"Queue full");
+            }
+            return (0x00);
+        }
+        return (0x00);
+
+    }
+    key800queue keyboard::getKeyboardKeycode(uint8_t abc800key){
+            key800queue outkey;
+            outkey.modifier =0x00;
+            outkey.special = 0x00;
+            outkey.ascii = abc800key;
+            if (abc800key >=0x61 && abc800key <=0x7a){ // small letter
+                return (outkey);
+            }
+            if (abc800key >=0x41 && abc800key <=0x5a){ // Capital
+                return (outkey);
+            }
+            if (abc800key >=0x30 && abc800key <=0x39){ // Digits
+                return (outkey);
+            }
+            if (    abc800key >= 0x21 && 
+                    abc800key <= 0x29 && 
+                    abc800key != 0x24 && 
+                    abc800key != 0x27){ // Digits symbols
+                return (outkey);
+            }
+            switch(abc800key){
+   
+                case 0x2b:		// +
+                    return (outkey);
+                    break;
+                case 0x27:		// '
+                    return (outkey);
+                    break;
+                case 0x3f:		// ?
+                    return (outkey);
+                    break;
+                case 0x3d:		// ?
+                    return (outkey);
+                    break;
+                case 0x2d:		// -
+                    return (outkey);
+                    break;
+                case 0x2c:		// ,
+                    return (outkey);
+                    break;
+                case 0x2e:		// .
+                    return (outkey);
+                    break;
+                case 0x3b:		// ;
+                    return (outkey);
+                    break;
+                case 0x3c:		// ;
+                    return (outkey);
+                    break;
+                case 0x3a:		// :
+                    return (outkey);
+                    break;
+                case 0x5f:		// ?
+                    return (outkey);
+                    break;
+                case 0x2f:		// /
+                    return (outkey);
+                    break;                
+                 case 0x3e:      //<
+                    return (outkey);
+                    break;              
+                case 0x20:		// SPACE INVADERS
+                    return (outkey);
+                    break;
+                case 0x2a:		// * STAR WARS
+                    return (outkey);
+                    break;
+                //None Match
+                case 0x0d:
+                    outkey.ascii = 0x13;
+                    return (outkey);
+                    break;
+    
+                case 0x40:		// É
+                    outkey.ascii = 0xe9;
+                    return (outkey);
+                    break;
+                case 0x60:		// é
+                    outkey.ascii = 0xc9;
+                    return (outkey);
+                    break;
+                case 0x5e:		// Ü
+                    outkey.ascii = 0xfc;
+                    return (outkey);
+                    break;
+                case 0x7e:		// ü
+                    outkey.ascii = 0xdc;
+                    return (outkey);
+                    break;
+                case 0x24:		// ¤
+                    outkey.ascii = 0xa4;
+                    return (outkey);
+                    break;
+                case 0x7c:      //ö
+                    outkey.ascii = 0xf6;
+                    return (outkey);
+                    break; 
+                case 0x5c:      //Ö
+                    outkey.ascii = 0xd6;
+                    return (outkey);
+                    break;  
+                case 0x7d:      //å
+                    outkey.ascii = 0xe5;
+                    return (outkey);
+                    break; 
+                case 0x5d:      //Å
+                    outkey.ascii = 0xc5;
+                    return (outkey);
+                    break;  
+                case 0x7b:      //ä
+                    outkey.ascii = 0xe4;
+                    return (outkey);
+                    break; 
+                case 0x5b:      //Ä
+                    outkey.ascii = 0xc4;
+                    return (outkey);
+                    break;    
+            }
+            
+            //CTRL modifier + char
+            if (abc800key>=0x01 && abc800key<=0x1a){
+                outkey.ascii=abc800key+0x60;
+                outkey.modifier=0x01;
+                return (outkey);  
+            }
+            switch (abc800key) {
+                case 0x1f:      // O // F4 ABC82
+                    outkey.modifier=0x03;
+                    outkey.ascii=0x4f;
+                    return (outkey);
+                    break; 
+                case 0x00:      // P
+                    outkey.modifier=0x03;
+                    outkey.ascii=0x50;
+                    return (outkey);
+                    break; 
+                case 0x1b:      // K
+                    outkey.modifier=0x03;
+                    outkey.ascii=0x4b;
+                    return (outkey);
+                    break; 
+                case 0x1c:      // L // F1 ABC82
+                    outkey.modifier=0x03;
+                    outkey.ascii=0x4c;
+                    return (outkey);
+                    break; 
+                case 0x1e:      // N // F3 ABC82
+                    outkey.modifier=0x03;
+                    outkey.ascii=0x4e;
+                    return (outkey);
+                    break; 
+                case 0x1d:      // M // F2ABC82
+                    outkey.modifier=0x03;
+                    outkey.ascii=0x4d;
+                    return (outkey);
+                    break; 
+            }
+            outkey.ascii = 0x00;
+            return (outkey);
+
+    }
+    uint8_t keyboard::getABC800Keycode(key800queue keyqueue) {
+        //Observe order fall through
+        if (!keyqueue.modifier ||  keyqueue.modifier == 0x02 ||  keyqueue.modifier == 0x20){ // handle ascii chart with modifer for shift etc
+            if (keyqueue.ascii >=0x61 && keyqueue.ascii <=0x7a){ // small letter
+                return (keyqueue.ascii);
+            }
+            if (keyqueue.ascii >=0x41 && keyqueue.ascii <=0x5a){ // Capital
+                return (keyqueue.ascii);
+            }
+            if (keyqueue.ascii >=0x30 && keyqueue.ascii <=0x39){ // Digits
+                return (keyqueue.ascii);
+            }
+            if (keyqueue.ascii >=0x21 && keyqueue.ascii <=0x29 && keyqueue.ascii!=0x24 && keyqueue.ascii!=0x27){ // Digits symbols
+                return (keyqueue.ascii);
+            }
+            
+            switch (keyqueue.ascii) {
+                case 0xe9:		// É
+                    return 0x40;
+                    break;
+                case 0xc9:		// é
+                    return 0x60;
+                    break;
+                case 0xfc:		// Ü
+                    return 0x5e;
+                    break;
+                case 0xdc:		// ü
+                    return 0x7e;
+                    break;
+                case 0xa4:		// ¤
+                    return 0x24;
+                    break;
+                case 0x13:		// ENTER CYPER SPACE
+                    return 0x0d;
+                    break;
+                case 0xf6:      //ö
+                    return 0x7c;
+                    break; 
+                case 0xD6:      //Ö
+                    return 0x5c;
+                    break;  
+                case 0xe5:      //å
+                    return 0x7d;
+                    break; 
+                case 0xC5:      //Å
+                    return 0x5d;
+                    break;  
+                case 0xe4:      //ä
+                    return 0x7b;
+                    break; 
+                case 0xC4:      //Ä
+                    return 0x5b;
+                    break;    
+                //From here the ABC80 matches     
+                case 0x2b:		// +
+                    return 0x2b;
+                    break;
+                case 0x27:		// '
+                    return 0x27;
+                    break;
+                case 0x3f:		// ?
+                    return 0x3f;
+                    break;
+                case 0x3d:		// ?
+                    return 0x3d;
+                    break;
+                case 0x2d:		// -
+                    return 0x2d;
+                    break;
+                case 0x2c:		// ,
+                    return 0x2c;
+                    break;
+                case 0x2e:		// .
+                    return 0x2e;
+                    break;
+                case 0x3b:		// ;
+                    return 0x3b;
+                    break;
+                case 0x3c:		// ;
+                    return 0x3c;
+                    break;
+                case 0x3a:		// :
+                    return 0x3a;
+                    break;
+                case 0x5f:		// ?
+                    return 0x5f;
+                    break;
+                case 0x2f:		// /
+                    return 0x2f;
+                    break;
+                 case 0x3e:      //<
+                    return 0x3e;
+                    break;              
+                case 0x20:		// SPACE INVADERS
+                    return 0x20;
+                    break;
+                case 0x2a:		// * STAR WARS
+                    return 0x2a;
+                    break;
+                case 0x4c:		// * STAR WARS
+                    return 0x18;
+                    break;
+            }
+        }
+
+        //CTRL - C or escape
+        
+        if ((keyqueue.modifier == 0x01 && (keyqueue.ascii == 0x63 || keyqueue.ascii == 0x43)) || keyqueue.special == 0x29 ){
+            return 0x03; 
+        }
+        //CTRL modifier + char
+        if (keyqueue.modifier == 0x01 ){
+            if (keyqueue.ascii >=0x61 && keyqueue.ascii <=0x7a){ //
+                return (keyqueue.ascii-0x60);
+            }   
+        }
+        //CTRL SHIFT modifier + char
+        if (keyqueue.modifier == 0x03 || keyqueue.modifier == 0x33 ){
+             switch (keyqueue.ascii) {
+                case 0x4f:      // O
+                    return 0x1f;
+                    break; 
+                case 0x50:      // P
+                    return 0x00;
+                    break; 
+                case 0x4b:      // K
+                    return 0x1b;
+                    break; 
+                case 0x4c:      // L
+                    return 0x1c;
+                    break; 
+                case 0x4e:      // N
+                    return 0x1e;
+                    break; 
+                case 0x4d:      // M
+                    return 0x1d;
+                    break; 
+             }
+        }
+        // Backspace
+        if (keyqueue.special == 0x2a )
+            return(0x08);
+        // Delete
+        if (keyqueue.special == 0x4c )
+            return(0x18);
+        
+        return (0xFF);
+    }
+}

+ 78 - 0
sw/key800/src/abc800_littlefs.cpp

@@ -0,0 +1,78 @@
+#include <Arduino.h>
+#include "abc800_keyboard.h"
+#include "abc800_littlefs.h"
+#include "abc800_config.h"
+#include <ArduinoJson.h> 
+#include <FS.h> 
+#include "LITTLEFS.h"
+
+static const char TAG[] = __FILE__;
+#define FORMAT_LITTLEFS_IF_FAILED true
+void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
+    Serial.printf("Listing directory: %s\r\n", dirname);
+
+    File root = fs.open(dirname);
+    if(!root){
+        ESP_LOGE(TAG,"- failed to open directory");
+        return;
+    }
+    if(!root.isDirectory()){
+        ESP_LOGE(TAG," - not a directory");
+        return;
+    }
+
+    File file = root.openNextFile();
+    while(file){
+        if(file.isDirectory()){
+            ESP_LOGD(TAG,"  DIR : ");
+
+#ifdef CONFIG_LITTLEFS_FOR_IDF_3_2
+            Serial.println(file.name());
+#else
+            Serial.print(file.name());
+            time_t t= file.getLastWrite();
+            struct tm * tmstruct = localtime(&t);
+            ESP_LOGD(TAG,"  LAST WRITE: %d-%02d-%02d %02d:%02d:%02d\n",(tmstruct->tm_year)+1900,( tmstruct->tm_mon)+1, tmstruct->tm_mday,tmstruct->tm_hour , tmstruct->tm_min, tmstruct->tm_sec);
+#endif
+
+            if(levels){
+                listDir(fs, file.name(), levels -1);
+            }
+        } else {
+            ESP_LOGD(TAG,"  FILE: %s ",file.name());
+            ESP_LOGD(TAG,"  SIZE: %d",file.size());
+
+#ifdef CONFIG_LITTLEFS_FOR_IDF_3_2
+            Serial.println(file.size());
+#else
+            time_t t= file.getLastWrite();
+            struct tm * tmstruct = localtime(&t);
+            ESP_LOGD(TAG,"  LAST WRITE: %d-%02d-%02d %02d:%02d:%02d\n",(tmstruct->tm_year)+1900,( tmstruct->tm_mon)+1, tmstruct->tm_mday,tmstruct->tm_hour , tmstruct->tm_min, tmstruct->tm_sec);
+#endif
+        }
+        file = root.openNextFile();
+    }
+}
+namespace abc800
+{
+
+    void littlefs::setup(){
+        //clean FS, for testing
+        //SPIFFS.format();
+
+        delay(1000);
+        ESP_LOGD(TAG,"TAG");
+        if(!LITTLEFS.begin(FORMAT_LITTLEFS_IF_FAILED)){
+            ESP_LOGD(TAG,"LITTLEFS Mount Failed");
+        }
+        listDir(LITTLEFS, "/", 0);
+
+        ESP_LOGD(TAG,"TEST");
+        delay(1000);
+        
+    }
+    key800config littlefs::loadconfig(){
+        key800config config;
+        return (config);   
+    }
+}

+ 130 - 0
sw/key800/src/abc800_usb.cpp

@@ -0,0 +1,130 @@
+#include <Arduino.h>
+#include "abc800_usb.h"
+#include "port.h"
+#include "keyboardpipe.h"
+#include "abc800_hidboot.h"
+#include "abc800_config.h"
+
+extern USBHostKeyboard port;
+extern TimerHandle_t repeat_delay_timer_out;
+extern TimerHandle_t repeat_timer_out;
+extern KeyboardReportParser keyparse;
+extern TaskHandle_t Keyboard_Task_Out;
+extern QueueHandle_t abc800_key_queue_out;
+uint8_t old_key = 0x00;
+key800queue key800queueold; 
+
+void  onRepeatTimer_out(TimerHandle_t xTimer) {
+  if (xQueueSend(abc800_key_queue_out, (void *)&key800queueold, 0) != pdTRUE) {
+    ESP_LOGE(TAG,"Queue full");
+  }
+   xTimerStart(repeat_timer_out, portMAX_DELAY);
+}
+void ctrl_pipe_cb(ext_pipe_event_msg_t event, usb_irp_t *irp)
+{
+  ESP_LOGI(TAG,"CTRL EVENT: 0x%x\n", event);
+  if (event == 0xA206)
+  {
+    port.getProductString();
+    delay(100);
+    port.getManufacturerString();
+    delay(100);
+    port.inpipe->inData();
+    
+  }
+}
+void onProductString(char *str)
+{
+  ESP_LOGI(TAG,"%s",str);
+}
+
+void onManufacturerString(char *str)
+{
+   ESP_LOGI(TAG,"%s",str);
+}
+void usbh_keyboard_device_ready()
+{
+  port.setIdle();
+  port.getHidReportMap();
+  ESP_LOGI(TAG,"Keyboard ready");
+
+}
+
+static void hid_dataout_cb(ext_pipe_event_msg_t event, usb_irp_t *irp)
+{
+      ESP_LOGI(TAG,"DATA out return");
+}
+
+static void hid_datain_cb(ext_pipe_event_msg_t event, usb_irp_t *irp)
+{
+    switch (event)
+    {
+    case HCD_PIPE_EVENT_IRP_DONE:
+        ESP_LOGD(TAG, "modi: %i", (int8_t)irp->data_buffer[0]);
+        ESP_LOGD(TAG, "Key1: %i", (int8_t)irp->data_buffer[2]);
+        ESP_LOGD(TAG, "Key2: %i", (int8_t)irp->data_buffer[3]);
+        ESP_LOGD(TAG, "Key3: %i", (int8_t)irp->data_buffer[4]);
+        ESP_LOGD(TAG, "Key4: %i", (int8_t)irp->data_buffer[5]);
+        ESP_LOGD(TAG, "Key5: %i", (int8_t)irp->data_buffer[6]);
+        ESP_LOGD(TAG, "Key6: %i", (int8_t)irp->data_buffer[7]);
+        if (!(int8_t)irp->data_buffer[2]){
+          xTimerStop(repeat_delay_timer_out, portMAX_DELAY);
+          xTimerStop(repeat_timer_out, portMAX_DELAY);
+        }
+        uint8_t key =irp->data_buffer[2];
+        uint8_t mod =irp->data_buffer[0];
+        keyparse.Parse(irp->num_bytes,irp->data_buffer,&port);
+        if (key!=0){
+          digitalWrite(2,1);
+          ESP_LOGD(TAG,"Key number0x%02x\r\n",(char)irp->data_buffer[2]);
+          key800queue key800queue ;
+          key800queue.ascii = keyparse.OemToAscii(mod,key);
+          key800queue.special = 0x00;
+          key800queue.modifier = mod;
+          if (key800queue.ascii== 0x00 && key!=0x00){
+            key800queue.special = key;
+          }
+          if (key!=0x00 || key800queue.special!=0x00){
+            
+            if (old_key!=key)
+            {
+              if (xQueueSend(abc800_key_queue_out, (void *)&key800queue, 0) != pdTRUE) {
+                ESP_LOGE(TAG,"Queue full");
+              }
+              key800queueold=key800queue;
+              xTimerStart(repeat_delay_timer_out, portMAX_DELAY);
+            }
+            
+          }
+ 
+        }else{
+          digitalWrite(2,0);
+        }
+        old_key=key;
+
+    }
+}
+
+void usb_port_cb(port_event_msg_t msg, USBHostPort *port)
+{
+  ESP_LOGI(TAG,"PORT EVENT: 0x%x\n", msg.port_event);
+}
+void abc800_port_init(){
+        port.onPortEvent(usb_port_cb);
+    port.onControlEvent(ctrl_pipe_cb);
+    port.onDataIn(hid_datain_cb);
+    port.onDataOut(hid_dataout_cb);
+    repeat_timer_out = xTimerCreate(
+                      "Repeat Timer",        // Name of timer
+                      ABC800_KEY_REPEAT_TIME / portTICK_PERIOD_MS,  // Period of timer (in ticks)
+                      pdFALSE,                     // Auto-reload
+                      (void *)0,                  // Timer ID
+                      onRepeatTimer_out); 
+    repeat_delay_timer_out = xTimerCreate(
+                      "Repeat Delay Timer",           // Name of timer
+                      ABC800_KEY_REPEAT_TIME_DELAY/ portTICK_PERIOD_MS,  // Period of timer (in ticks)
+                      pdFALSE,                    // Auto-reload
+                      (void *)1,                  // Timer ID
+                      onRepeatTimer_out); 
+                      
+}

+ 162 - 0
sw/key800/src/abc800_web.cpp

@@ -0,0 +1,162 @@
+#include <Arduino.h>
+#include <WiFi.h>
+#include <AsyncTCP.h>
+#include <ESPAsyncWebServer.h>
+#include <AsyncElegantOTA.h>
+#include "abc800_config.h"
+#include "abc800_web.h"
+#include "abc800_func.h"
+#include "abc800_webcontent.h"
+#include "abc800_hidboot.h"
+#include <WebSerial.h>
+#include "ESPAsyncWiFiManager.h"
+static const char TAG[] = __FILE__;
+
+extern WiFiClass WiFi;
+extern AsyncWebServer server;
+extern abc800::web abc800_web;
+extern abc800::std_abc800 abc800_std;
+extern TaskHandle_t OTA_Task ;
+extern KeyboardReportParser keyparse;
+extern QueueHandle_t abc800_key_queue_out;
+extern AsyncElegantOtaClass AsyncElegantOTA;
+extern DNSServer dns;
+extern AsyncWiFiManager wifiManager;
+extern AsyncWebSocket ws("/ws");
+
+
+namespace abc800
+{
+    void OTAtask(void * parameter){
+        AsyncElegantOTA.loop();
+        ws.cleanupClients();
+    }
+    const char* PARAM_INPUT_1 = "input1";
+    const char* PARAM_INPUT_2 = "input2";
+    const char* PARAM_INPUT_3 = "input3";
+    void WSrecvMsg(uint8_t *data, size_t len){
+        String d = "";
+        key800queue key800queue;
+        uint8_t * decode_data =(uint8_t * ) malloc( len);
+        uint8_t length = abc800_std.processutf8String(data,len,decode_data);
+        //Serial.println(length);
+        for(int i=0; i <= length; i++){
+            //Serial.println(decode_data[i]);
+            key800queue.ascii = decode_data[i];
+            key800queue.special = 0x00;
+            key800queue.modifier = 0x00;
+            if (xQueueSend(abc800_key_queue_out, (void *)&key800queue, 0) != pdTRUE) {
+                ESP_LOGE(TAG,"Queue full");
+            }
+        }
+        key800queue.ascii = 0x13;
+        if (xQueueSend(abc800_key_queue_out, (void *)&key800queue, 0) != pdTRUE) {
+                ESP_LOGE(TAG,"Queue full");
+        }
+        for(int i=0; i <len; i++){
+            d+= char(data[i]);
+        }
+        WebSerial.print(d+"\r\n");
+    }
+    
+    
+    void web::notifyClients() {
+        //ws.textAll(String(ledState));
+    }
+
+    void web::handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
+        AwsFrameInfo *info = (AwsFrameInfo*)arg;
+        if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
+            data[len] = 0;
+            if (strcmp((char*)data, "toggle") == 0) {
+            //ledState = !ledState;
+            notifyClients();
+            }
+        }
+    }
+    void web::onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type,void *arg, uint8_t *data, size_t len) {
+        switch (type) {
+            case WS_EVT_CONNECT:
+            Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
+            connected = true;
+            break;
+            case WS_EVT_DISCONNECT:
+            Serial.printf("WebSocket client #%u disconnected\n", client->id());
+            connected = false;
+            break;
+            case WS_EVT_DATA:
+            handleWebSocketMessage(arg, data, len);
+            break;
+            case WS_EVT_PONG:
+            case WS_EVT_ERROR:
+            break;
+        }
+    }
+
+    void web::initWebSocket() {
+        //ws.onEvent(abc800_web.onEvent);
+        server.addHandler(&ws);
+    }
+    String web::processor(const String& var){
+        return "X";
+    }
+    void web::sendwebserial(uint8_t test){
+        //if(connected)
+            WebSerial.print((String)((char)test));
+    }  
+    void web::fbuttonsinit(){
+
+        server.on("/fbuttons", HTTP_GET, [](AsyncWebServerRequest *request){
+        request->send_P(200, "text/html", fbuttons_html); 
+        });
+
+        server.on("/fbuttonsget", HTTP_GET, [] (AsyncWebServerRequest *request) {
+            String inputMessage;
+            String inputParam;
+            // GET input1 value on <ESP_IP>/get?input1=<inputMessage>
+            if (request->hasParam(PARAM_INPUT_1)) {
+            inputMessage = request->getParam(PARAM_INPUT_1)->value();
+            inputParam = PARAM_INPUT_1;
+            }
+            // GET input2 value on <ESP_IP>/get?input2=<inputMessage>
+            else if (request->hasParam(PARAM_INPUT_2)) {
+            inputMessage = request->getParam(PARAM_INPUT_2)->value();
+            inputParam = PARAM_INPUT_2;
+            }
+            // GET input3 value on <ESP_IP>/get?input3=<inputMessage>
+            else if (request->hasParam(PARAM_INPUT_3)) {
+            inputMessage = request->getParam(PARAM_INPUT_3)->value();
+            inputParam = PARAM_INPUT_3;
+            }
+            else {
+            inputMessage = "No message sent";
+            inputParam = "none";
+            }
+            Serial.println(inputMessage);
+            request->send(200, "text/html", "HTTP GET request sent to your ESP on input field (" 
+                                            + inputParam + ") with value: " + inputMessage +
+                                            "<br><a href=\"/\">Return to Home Page</a>");
+        });
+    }
+    void web::init(){
+
+        initWebSocket();
+
+        // Route for root / web page
+        server.on("/command", HTTP_GET, [](AsyncWebServerRequest *request){
+            request->send_P(200, "text/html", index_html);//, processor);
+        });
+
+        // Start ElegantOTA
+        
+        // Start server
+        server.begin();
+        AsyncElegantOTA.begin(&server);
+        WebSerial.begin(&server);
+    	WebSerial.msgCallback(WSrecvMsg);
+        fbuttonsinit();
+        wifiManager.setupConfig();
+
+    }
+
+}

+ 39 - 0
sw/key800/src/abc800_wifi.cpp

@@ -0,0 +1,39 @@
+#include <Arduino.h>
+#include "abc800_wifi.h"
+#include "abc800_func.h"
+#include "WiFi.h"
+#include "abc800_config.h"
+#include "abc800_callback.h"
+#include "abc800_web.h"
+#include "abc800_hidboot.h"
+#include <esp_wifi.h>
+#include <DNSServer.h>
+#include <ESPAsyncWiFiManager.h> 
+static const char TAG[] = __FILE__;
+extern WiFiClass WiFi;
+extern key800config abc800_configuration;
+extern TaskHandle_t WiFi_Task;
+extern abc800::std_abc800 abc800_std;
+extern abc800::web abc800_web;
+extern abc800::callback abc800_callback;
+extern AsyncWebServer server;
+extern DNSServer dns;
+extern AsyncWiFiManager wifiManager;
+namespace abc800 {
+	void wificonnect(){
+		ESP_LOGI(TAG,"WiFi Connected start web");
+		if (!abc800_web.connected){
+			abc800_web.init();
+			abc800_web.connected=true;
+		}
+	}
+	void wifi::setup(){
+		
+		wifiManager.setConnectCallback(wificonnect);
+		wifiManager.autoConnect(ABC800_KEY_WIFI_SSID, ABC800_KEY_WIFI_PSK);
+	}	
+	
+	
+
+}
+

+ 84 - 0
sw/key800/src/main.cpp

@@ -0,0 +1,84 @@
+#include "Arduino.h"
+#include "port.h"
+#include "keyboardpipe.h"
+#include "esp32-hal-log.h"
+#include "parse_hid.h"
+#include "abc800_hidboot.h"
+#include "WiFi.h"
+#include "abc800_func.h"
+#include "abc800_wifi.h"
+#include "abc800_callback.h"
+#include "abc800_web.h"
+#include "abc800_keyboard.h"
+#include "abc800_gpio.h"
+#include "abc800_usb.h"
+#include "abc800_littlefs.h"
+#define LOG_LOCAL_LEVEL ESP_LOG_INFO
+#include "esp_log.h"
+#include <AsyncElegantOTA.h>
+#define DEVICE_ADDRESS 2
+static const char TAG[] = __FILE__;
+
+USBHostKeyboard port(2);
+KeyboardReportParser keyparse;
+abc800::wifi abc800_wifi;
+abc800::std_abc800 abc800_std;
+abc800::callback abc800_callback;
+abc800::web abc800_web;
+abc800::keyboard abc800_keyboard;
+abc800::gpio abc800_gpio;
+abc800::littlefs abc800_littlefs;
+key800config abc800_configuration;
+AsyncElegantOtaClass AsyncElegantOTA;
+DNSServer dns;
+AsyncWebServer server(80);
+AsyncWiFiManager wifiManager(&server,&dns);
+
+TaskHandle_t WiFi_Task = NULL;
+TaskHandle_t Web_Task = NULL;
+TaskHandle_t OTA_Task = NULL;
+TaskHandle_t Keyboard_Task_Out = NULL;
+TaskHandle_t Keyboard_ABC800_Task_In = NULL;
+TaskHandle_t Config_Task = NULL;
+
+TimerHandle_t repeat_delay_timer_out = NULL;
+TimerHandle_t repeat_timer_out = NULL;
+TimerHandle_t abc800_repeat_delay_timer = NULL;
+TimerHandle_t abc800_repeat_timer = NULL;
+TimerHandle_t abc800_check_timer = NULL;
+
+
+String macaddr;
+
+QueueHandle_t abc800_key_queue_out = NULL;
+QueueHandle_t abc800_key_queue_in = NULL;
+
+portMUX_TYPE sync_isr = portMUX_INITIALIZER_UNLOCKED;
+
+void setup()
+{
+  Serial.begin(KEY800_SERIAL_BAUD_RATE);
+  Serial1.begin(650,SERIAL_8N2,A800_RXD,A800_TXD);
+  abc800_littlefs.setup();
+  abc800_littlefs.loadconfig();
+  abc800_gpio.init();
+
+  abc800_key_queue_out = xQueueCreate(ABC800_KEY_OUT_BUFFER_SIZE, sizeof(key800queue));
+  abc800_key_queue_in = xQueueCreate(ABC800_KEY_IN_BUFFER_SIZE, sizeof(key800queue));               
+
+  if(port.init())
+  {
+    abc800_port_init();
+  }
+  
+  digitalWrite(KEY800_LED,HIGH);
+  abc800_keyboard.init();
+  abc800_wifi.setup();
+  digitalWrite(A800_RST,HIGH);
+
+}
+
+void loop()
+{
+    delay(1000);
+}

+ 12 - 0
sw/key800/variants/key800/partitions.csv

@@ -0,0 +1,12 @@
+# ESP-IDF Partition Table
+# Name,   Type, SubType, Offset,  Size, Flags
+# bootloader.bin,,          0x1000, 32K
+# partition table,          0x8000, 4K
+# Name,     Type,   SubType,    Offset,     Size,     Flags
+
+nvs,      data, nvs,      0x9000,  20K,
+otadata,  data, ota,      0xe000,  8K,
+ota_0,    0,    ota_0,   0x10000,  1408K,
+ota_1,    0,    ota_1,  0x170000,  1408K,
+uf2,      app,  factory,0x2d0000,  256K,
+spiffs,     data, 	spiffs,    0x310000,  960K,

+ 67 - 0
sw/key800/variants/key800/pins_arduino.h

@@ -0,0 +1,67 @@
+#ifndef Pins_Arduino_h
+#define Pins_Arduino_h
+
+#include <stdint.h>
+
+
+#define USB_VID 0x239A
+#define USB_PID 0x80DF
+#define USB_MANUFACTURER "SweProj"
+#define USB_PRODUCT "KEY80 ESP32-S2"
+#define USB_SERIAL ""
+
+
+#define EXTERNAL_NUM_INTERRUPTS 46
+#define NUM_DIGITAL_PINS        48
+#define NUM_ANALOG_INPUTS       20
+
+#define analogInputToDigitalPin(p)  (((p)<20)?(esp32_adc2gpio[(p)]):-1)
+#define digitalPinToInterrupt(p)    (((p)<48)?(p):-1)
+#define digitalPinHasPWM(p)         (p < 46)
+
+
+static const uint8_t TX = 37;
+static const uint8_t RX = 38;
+
+
+static const uint8_t A0 = 17;
+static const uint8_t A1 = 18;
+static const uint8_t A2 = 1;
+static const uint8_t A3 = 2;
+static const uint8_t A4 = 3;
+static const uint8_t A5 = 4;
+static const uint8_t A6 = 5;
+static const uint8_t A7 = 6;
+static const uint8_t A8 = 7;
+static const uint8_t A9 = 8;
+static const uint8_t A10 = 9;
+static const uint8_t A11 = 10;
+static const uint8_t A12 = 11;
+static const uint8_t A13 = 12;
+static const uint8_t A14 = 13;
+static const uint8_t A15 = 14;
+static const uint8_t A16 = 15;
+static const uint8_t A17 = 16;
+static const uint8_t A18 = 19;
+static const uint8_t A19 = 20;
+
+
+static const uint8_t T1 = 1;
+static const uint8_t T2 = 2;
+static const uint8_t T3 = 3;
+static const uint8_t T4 = 4;
+static const uint8_t T5 = 5;
+static const uint8_t T6 = 6;
+static const uint8_t T7 = 7;
+static const uint8_t T8 = 8;
+static const uint8_t T9 = 9;
+static const uint8_t T10 = 10;
+static const uint8_t T11 = 11;
+static const uint8_t T12 = 12;
+static const uint8_t T13 = 13;
+static const uint8_t T14 = 14;
+
+static const uint8_t DAC1 = 17;
+static const uint8_t DAC2 = 18;
+
+#endif /* Pins_Arduino_h */

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff