first commit
This commit is contained in:
commit
0b89d84626
BIN
favicon.png
Normal file
BIN
favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 684 B |
160
index.html
Normal file
160
index.html
Normal file
@ -0,0 +1,160 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Esp32 FM pirate</title>
|
||||
<style>
|
||||
* {
|
||||
color: #7295c4;
|
||||
border-color: #353535;
|
||||
background: #121212;
|
||||
}
|
||||
h1 {
|
||||
font-size: 18px;
|
||||
}
|
||||
.main {
|
||||
background: #212121;
|
||||
height: auto;
|
||||
}
|
||||
.main header {
|
||||
height: auto;
|
||||
padding: 15px 16px;
|
||||
justify-content: center;
|
||||
}
|
||||
.main .content {
|
||||
padding: 20px;
|
||||
}
|
||||
.main {
|
||||
border: 3px solid;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
width: 22em;
|
||||
min-width: 300px;
|
||||
margin: 50px auto;
|
||||
height: auto;
|
||||
text-align: center;
|
||||
}
|
||||
.form-row {
|
||||
padding: 4px 0;
|
||||
}
|
||||
.form-row > label {
|
||||
display: block;
|
||||
line-height: 2em;
|
||||
}
|
||||
.form-row > input {
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
input {
|
||||
border: 2px solid;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* далее стили для слидеров */
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
height: 34px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #2196F3;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="main">
|
||||
<header><h1>Lamps4 by Vlados31 & ESP32</h1></header>
|
||||
<div class="content">
|
||||
<div id="form-content">
|
||||
|
||||
<div class="form-row">
|
||||
<label class="required" for="control-rds">RDS текст</label>
|
||||
<input type="text" maxlength="16" size="20" id="control-rds" onchange="sendParam('rds', document.getElementById('control-rds').value)">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="required" for="control-frq">Частота (96.00fm = 9600)</label>
|
||||
<input type="number" maxlength="5" id="control-frq" onchange="sendParam('frq', document.getElementById('control-frq').value)">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="control-fmen">FM Enable</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="control-fmen" onchange="sendParam('fmen', document.getElementById('control-fmen').checked ? 1 : 0)">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function sendParam(io_interface, value) {
|
||||
let xhr = new XMLHttpRequest();
|
||||
let url = `/`
|
||||
if (io_interface !== null) {
|
||||
url += `?${io_interface}=${encodeURIComponent(value)}`
|
||||
}
|
||||
xhr.onload = function () {
|
||||
if (xhr.status !== 200) {
|
||||
alert(`Ошибка ${xhr.status}: ${xhr.statusText}`);
|
||||
} else {
|
||||
let res = JSON.parse(xhr.response)
|
||||
if ("frq" in res) {
|
||||
document.getElementById('control-frq').value = res["frq"]
|
||||
}
|
||||
if ("rds" in res) {
|
||||
document.getElementById('control-rds').value = res["rds"]
|
||||
}
|
||||
if ("fmen" in res) {
|
||||
document.getElementById('control-fmen').checked = !!res["fmen"]
|
||||
}
|
||||
}
|
||||
}
|
||||
xhr.open('POST', url)
|
||||
xhr.send()
|
||||
}
|
||||
|
||||
sendParam(null)
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
17
platformio.ini
Normal file
17
platformio.ini
Normal file
@ -0,0 +1,17 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:esp32doit-devkit-v1]
|
||||
platform = espressif32
|
||||
board = esp32doit-devkit-v1
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
WiFi
|
||||
esp_tls
|
365
src/Adafruit_BusIO-master/Adafruit_BusIO_Register.cpp
Normal file
365
src/Adafruit_BusIO-master/Adafruit_BusIO_Register.cpp
Normal file
@ -0,0 +1,365 @@
|
||||
#include "Adafruit_BusIO_Register.h"
|
||||
|
||||
#if !defined(SPI_INTERFACES_COUNT) || \
|
||||
(defined(SPI_INTERFACES_COUNT) && (SPI_INTERFACES_COUNT > 0))
|
||||
|
||||
/*!
|
||||
* @brief Create a register we access over an I2C Device (which defines the
|
||||
* bus and address)
|
||||
* @param i2cdevice The I2CDevice to use for underlying I2C access
|
||||
* @param reg_addr The address pointer value for the I2C/SMBus register, can
|
||||
* be 8 or 16 bits
|
||||
* @param width The width of the register data itself, defaults to 1 byte
|
||||
* @param byteorder The byte order of the register (used when width is > 1),
|
||||
* defaults to LSBFIRST
|
||||
* @param address_width The width of the register address itself, defaults
|
||||
* to 1 byte
|
||||
*/
|
||||
Adafruit_BusIO_Register::Adafruit_BusIO_Register(Adafruit_I2CDevice *i2cdevice,
|
||||
uint16_t reg_addr,
|
||||
uint8_t width,
|
||||
uint8_t byteorder,
|
||||
uint8_t address_width) {
|
||||
_i2cdevice = i2cdevice;
|
||||
_spidevice = nullptr;
|
||||
_addrwidth = address_width;
|
||||
_address = reg_addr;
|
||||
_byteorder = byteorder;
|
||||
_width = width;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Create a register we access over an SPI Device (which defines the
|
||||
* bus and CS pin)
|
||||
* @param spidevice The SPIDevice to use for underlying SPI access
|
||||
* @param reg_addr The address pointer value for the SPI register, can
|
||||
* be 8 or 16 bits
|
||||
* @param type The method we use to read/write data to SPI (which is not
|
||||
* as well defined as I2C)
|
||||
* @param width The width of the register data itself, defaults to 1 byte
|
||||
* @param byteorder The byte order of the register (used when width is > 1),
|
||||
* defaults to LSBFIRST
|
||||
* @param address_width The width of the register address itself, defaults
|
||||
* to 1 byte
|
||||
*/
|
||||
Adafruit_BusIO_Register::Adafruit_BusIO_Register(Adafruit_SPIDevice *spidevice,
|
||||
uint16_t reg_addr,
|
||||
Adafruit_BusIO_SPIRegType type,
|
||||
uint8_t width,
|
||||
uint8_t byteorder,
|
||||
uint8_t address_width) {
|
||||
_spidevice = spidevice;
|
||||
_spiregtype = type;
|
||||
_i2cdevice = nullptr;
|
||||
_addrwidth = address_width;
|
||||
_address = reg_addr;
|
||||
_byteorder = byteorder;
|
||||
_width = width;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Create a register we access over an I2C or SPI Device. This is a
|
||||
* handy function because we can pass in nullptr for the unused interface,
|
||||
* allowing libraries to mass-define all the registers
|
||||
* @param i2cdevice The I2CDevice to use for underlying I2C access, if
|
||||
* nullptr we use SPI
|
||||
* @param spidevice The SPIDevice to use for underlying SPI access, if
|
||||
* nullptr we use I2C
|
||||
* @param reg_addr The address pointer value for the I2C/SMBus/SPI register,
|
||||
* can be 8 or 16 bits
|
||||
* @param type The method we use to read/write data to SPI (which is not
|
||||
* as well defined as I2C)
|
||||
* @param width The width of the register data itself, defaults to 1 byte
|
||||
* @param byteorder The byte order of the register (used when width is > 1),
|
||||
* defaults to LSBFIRST
|
||||
* @param address_width The width of the register address itself, defaults
|
||||
* to 1 byte
|
||||
*/
|
||||
Adafruit_BusIO_Register::Adafruit_BusIO_Register(
|
||||
Adafruit_I2CDevice *i2cdevice, Adafruit_SPIDevice *spidevice,
|
||||
Adafruit_BusIO_SPIRegType type, uint16_t reg_addr, uint8_t width,
|
||||
uint8_t byteorder, uint8_t address_width) {
|
||||
_spidevice = spidevice;
|
||||
_i2cdevice = i2cdevice;
|
||||
_spiregtype = type;
|
||||
_addrwidth = address_width;
|
||||
_address = reg_addr;
|
||||
_byteorder = byteorder;
|
||||
_width = width;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Write a buffer of data to the register location
|
||||
* @param buffer Pointer to data to write
|
||||
* @param len Number of bytes to write
|
||||
* @return True on successful write (only really useful for I2C as SPI is
|
||||
* uncheckable)
|
||||
*/
|
||||
bool Adafruit_BusIO_Register::write(uint8_t *buffer, uint8_t len) {
|
||||
|
||||
uint8_t addrbuffer[2] = {(uint8_t)(_address & 0xFF),
|
||||
(uint8_t)(_address >> 8)};
|
||||
|
||||
if (_i2cdevice) {
|
||||
return _i2cdevice->write(buffer, len, true, addrbuffer, _addrwidth);
|
||||
}
|
||||
if (_spidevice) {
|
||||
if (_spiregtype == ADDRESSED_OPCODE_BIT0_LOW_TO_WRITE) {
|
||||
// very special case!
|
||||
|
||||
// pass the special opcode address which we set as the high byte of the
|
||||
// regaddr
|
||||
addrbuffer[0] =
|
||||
(uint8_t)(_address >> 8) & ~0x01; // set bottom bit low to write
|
||||
// the 'actual' reg addr is the second byte then
|
||||
addrbuffer[1] = (uint8_t)(_address & 0xFF);
|
||||
// the address appears to be a byte longer
|
||||
return _spidevice->write(buffer, len, addrbuffer, _addrwidth + 1);
|
||||
}
|
||||
|
||||
if (_spiregtype == ADDRBIT8_HIGH_TOREAD) {
|
||||
addrbuffer[0] &= ~0x80;
|
||||
}
|
||||
if (_spiregtype == ADDRBIT8_HIGH_TOWRITE) {
|
||||
addrbuffer[0] |= 0x80;
|
||||
}
|
||||
if (_spiregtype == AD8_HIGH_TOREAD_AD7_HIGH_TOINC) {
|
||||
addrbuffer[0] &= ~0x80;
|
||||
addrbuffer[0] |= 0x40;
|
||||
}
|
||||
return _spidevice->write(buffer, len, addrbuffer, _addrwidth);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Write up to 4 bytes of data to the register location
|
||||
* @param value Data to write
|
||||
* @param numbytes How many bytes from 'value' to write
|
||||
* @return True on successful write (only really useful for I2C as SPI is
|
||||
* uncheckable)
|
||||
*/
|
||||
bool Adafruit_BusIO_Register::write(uint32_t value, uint8_t numbytes) {
|
||||
if (numbytes == 0) {
|
||||
numbytes = _width;
|
||||
}
|
||||
if (numbytes > 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// store a copy
|
||||
_cached = value;
|
||||
|
||||
for (int i = 0; i < numbytes; i++) {
|
||||
if (_byteorder == LSBFIRST) {
|
||||
_buffer[i] = value & 0xFF;
|
||||
} else {
|
||||
_buffer[numbytes - i - 1] = value & 0xFF;
|
||||
}
|
||||
value >>= 8;
|
||||
}
|
||||
return write(_buffer, numbytes);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Read data from the register location. This does not do any error
|
||||
* checking!
|
||||
* @return Returns 0xFFFFFFFF on failure, value otherwise
|
||||
*/
|
||||
uint32_t Adafruit_BusIO_Register::read(void) {
|
||||
if (!read(_buffer, _width)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t value = 0;
|
||||
|
||||
for (int i = 0; i < _width; i++) {
|
||||
value <<= 8;
|
||||
if (_byteorder == LSBFIRST) {
|
||||
value |= _buffer[_width - i - 1];
|
||||
} else {
|
||||
value |= _buffer[i];
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Read cached data from last time we wrote to this register
|
||||
* @return Returns 0xFFFFFFFF on failure, value otherwise
|
||||
*/
|
||||
uint32_t Adafruit_BusIO_Register::readCached(void) { return _cached; }
|
||||
|
||||
/*!
|
||||
* @brief Read a buffer of data from the register location
|
||||
* @param buffer Pointer to data to read into
|
||||
* @param len Number of bytes to read
|
||||
* @return True on successful write (only really useful for I2C as SPI is
|
||||
* uncheckable)
|
||||
*/
|
||||
bool Adafruit_BusIO_Register::read(uint8_t *buffer, uint8_t len) {
|
||||
uint8_t addrbuffer[2] = {(uint8_t)(_address & 0xFF),
|
||||
(uint8_t)(_address >> 8)};
|
||||
|
||||
if (_i2cdevice) {
|
||||
return _i2cdevice->write_then_read(addrbuffer, _addrwidth, buffer, len);
|
||||
}
|
||||
if (_spidevice) {
|
||||
if (_spiregtype == ADDRESSED_OPCODE_BIT0_LOW_TO_WRITE) {
|
||||
// very special case!
|
||||
|
||||
// pass the special opcode address which we set as the high byte of the
|
||||
// regaddr
|
||||
addrbuffer[0] =
|
||||
(uint8_t)(_address >> 8) | 0x01; // set bottom bit high to read
|
||||
// the 'actual' reg addr is the second byte then
|
||||
addrbuffer[1] = (uint8_t)(_address & 0xFF);
|
||||
// the address appears to be a byte longer
|
||||
return _spidevice->write_then_read(addrbuffer, _addrwidth + 1, buffer,
|
||||
len);
|
||||
}
|
||||
if (_spiregtype == ADDRBIT8_HIGH_TOREAD) {
|
||||
addrbuffer[0] |= 0x80;
|
||||
}
|
||||
if (_spiregtype == ADDRBIT8_HIGH_TOWRITE) {
|
||||
addrbuffer[0] &= ~0x80;
|
||||
}
|
||||
if (_spiregtype == AD8_HIGH_TOREAD_AD7_HIGH_TOINC) {
|
||||
addrbuffer[0] |= 0x80 | 0x40;
|
||||
}
|
||||
return _spidevice->write_then_read(addrbuffer, _addrwidth, buffer, len);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Read 2 bytes of data from the register location
|
||||
* @param value Pointer to uint16_t variable to read into
|
||||
* @return True on successful write (only really useful for I2C as SPI is
|
||||
* uncheckable)
|
||||
*/
|
||||
bool Adafruit_BusIO_Register::read(uint16_t *value) {
|
||||
if (!read(_buffer, 2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_byteorder == LSBFIRST) {
|
||||
*value = _buffer[1];
|
||||
*value <<= 8;
|
||||
*value |= _buffer[0];
|
||||
} else {
|
||||
*value = _buffer[0];
|
||||
*value <<= 8;
|
||||
*value |= _buffer[1];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Read 1 byte of data from the register location
|
||||
* @param value Pointer to uint8_t variable to read into
|
||||
* @return True on successful write (only really useful for I2C as SPI is
|
||||
* uncheckable)
|
||||
*/
|
||||
bool Adafruit_BusIO_Register::read(uint8_t *value) {
|
||||
if (!read(_buffer, 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*value = _buffer[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Pretty printer for this register
|
||||
* @param s The Stream to print to, defaults to &Serial
|
||||
*/
|
||||
void Adafruit_BusIO_Register::print(Stream *s) {
|
||||
uint32_t val = read();
|
||||
s->print("0x");
|
||||
s->print(val, HEX);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Pretty printer for this register
|
||||
* @param s The Stream to print to, defaults to &Serial
|
||||
*/
|
||||
void Adafruit_BusIO_Register::println(Stream *s) {
|
||||
print(s);
|
||||
s->println();
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Create a slice of the register that we can address without
|
||||
* touching other bits
|
||||
* @param reg The Adafruit_BusIO_Register which defines the bus/register
|
||||
* @param bits The number of bits wide we are slicing
|
||||
* @param shift The number of bits that our bit-slice is shifted from LSB
|
||||
*/
|
||||
Adafruit_BusIO_RegisterBits::Adafruit_BusIO_RegisterBits(
|
||||
Adafruit_BusIO_Register *reg, uint8_t bits, uint8_t shift) {
|
||||
_register = reg;
|
||||
_bits = bits;
|
||||
_shift = shift;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Read 4 bytes of data from the register
|
||||
* @return data The 4 bytes to read
|
||||
*/
|
||||
uint32_t Adafruit_BusIO_RegisterBits::read(void) {
|
||||
uint32_t val = _register->read();
|
||||
val >>= _shift;
|
||||
return val & ((1 << (_bits)) - 1);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Write 4 bytes of data to the register
|
||||
* @param data The 4 bytes to write
|
||||
* @return True on successful write (only really useful for I2C as SPI is
|
||||
* uncheckable)
|
||||
*/
|
||||
bool Adafruit_BusIO_RegisterBits::write(uint32_t data) {
|
||||
uint32_t val = _register->read();
|
||||
|
||||
// mask off the data before writing
|
||||
uint32_t mask = (1 << (_bits)) - 1;
|
||||
data &= mask;
|
||||
|
||||
mask <<= _shift;
|
||||
val &= ~mask; // remove the current data at that spot
|
||||
val |= data << _shift; // and add in the new data
|
||||
|
||||
return _register->write(val, _register->width());
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief The width of the register data, helpful for doing calculations
|
||||
* @returns The data width used when initializing the register
|
||||
*/
|
||||
uint8_t Adafruit_BusIO_Register::width(void) { return _width; }
|
||||
|
||||
/*!
|
||||
* @brief Set the default width of data
|
||||
* @param width the default width of data read from register
|
||||
*/
|
||||
void Adafruit_BusIO_Register::setWidth(uint8_t width) { _width = width; }
|
||||
|
||||
/*!
|
||||
* @brief Set register address
|
||||
* @param address the address from register
|
||||
*/
|
||||
void Adafruit_BusIO_Register::setAddress(uint16_t address) {
|
||||
_address = address;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Set the width of register address
|
||||
* @param address_width the width for register address
|
||||
*/
|
||||
void Adafruit_BusIO_Register::setAddressWidth(uint16_t address_width) {
|
||||
_addrwidth = address_width;
|
||||
}
|
||||
|
||||
#endif // SPI exists
|
105
src/Adafruit_BusIO-master/Adafruit_BusIO_Register.h
Normal file
105
src/Adafruit_BusIO-master/Adafruit_BusIO_Register.h
Normal file
@ -0,0 +1,105 @@
|
||||
#ifndef Adafruit_BusIO_Register_h
|
||||
#define Adafruit_BusIO_Register_h
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#if !defined(SPI_INTERFACES_COUNT) || \
|
||||
(defined(SPI_INTERFACES_COUNT) && (SPI_INTERFACES_COUNT > 0))
|
||||
|
||||
#include "Adafruit_I2CDevice.h"
|
||||
#include "Adafruit_SPIDevice.h"
|
||||
|
||||
typedef enum _Adafruit_BusIO_SPIRegType {
|
||||
ADDRBIT8_HIGH_TOREAD = 0,
|
||||
/*!<
|
||||
* ADDRBIT8_HIGH_TOREAD
|
||||
* When reading a register you must actually send the value 0x80 + register
|
||||
* address to the device. e.g. To read the register 0x0B the register value
|
||||
* 0x8B is sent and to write 0x0B is sent.
|
||||
*/
|
||||
AD8_HIGH_TOREAD_AD7_HIGH_TOINC = 1,
|
||||
|
||||
/*!<
|
||||
* ADDRBIT8_HIGH_TOWRITE
|
||||
* When writing to a register you must actually send the value 0x80 +
|
||||
* the register address to the device. e.g. To write to the register 0x19 the
|
||||
* register value 0x99 is sent and to read 0x19 is sent.
|
||||
*/
|
||||
ADDRBIT8_HIGH_TOWRITE = 2,
|
||||
|
||||
/*!<
|
||||
* ADDRESSED_OPCODE_LOWBIT_TO_WRITE
|
||||
* Used by the MCP23S series, we send 0x40 |'rd with the opcode
|
||||
* Then set the lowest bit to write
|
||||
*/
|
||||
ADDRESSED_OPCODE_BIT0_LOW_TO_WRITE = 3,
|
||||
|
||||
} Adafruit_BusIO_SPIRegType;
|
||||
|
||||
/*!
|
||||
* @brief The class which defines a device register (a location to read/write
|
||||
* data from)
|
||||
*/
|
||||
class Adafruit_BusIO_Register {
|
||||
public:
|
||||
Adafruit_BusIO_Register(Adafruit_I2CDevice *i2cdevice, uint16_t reg_addr,
|
||||
uint8_t width = 1, uint8_t byteorder = LSBFIRST,
|
||||
uint8_t address_width = 1);
|
||||
|
||||
Adafruit_BusIO_Register(Adafruit_SPIDevice *spidevice, uint16_t reg_addr,
|
||||
Adafruit_BusIO_SPIRegType type, uint8_t width = 1,
|
||||
uint8_t byteorder = LSBFIRST,
|
||||
uint8_t address_width = 1);
|
||||
|
||||
Adafruit_BusIO_Register(Adafruit_I2CDevice *i2cdevice,
|
||||
Adafruit_SPIDevice *spidevice,
|
||||
Adafruit_BusIO_SPIRegType type, uint16_t reg_addr,
|
||||
uint8_t width = 1, uint8_t byteorder = LSBFIRST,
|
||||
uint8_t address_width = 1);
|
||||
|
||||
bool read(uint8_t *buffer, uint8_t len);
|
||||
bool read(uint8_t *value);
|
||||
bool read(uint16_t *value);
|
||||
uint32_t read(void);
|
||||
uint32_t readCached(void);
|
||||
bool write(uint8_t *buffer, uint8_t len);
|
||||
bool write(uint32_t value, uint8_t numbytes = 0);
|
||||
|
||||
uint8_t width(void);
|
||||
|
||||
void setWidth(uint8_t width);
|
||||
void setAddress(uint16_t address);
|
||||
void setAddressWidth(uint16_t address_width);
|
||||
|
||||
void print(Stream *s = &Serial);
|
||||
void println(Stream *s = &Serial);
|
||||
|
||||
private:
|
||||
Adafruit_I2CDevice *_i2cdevice;
|
||||
Adafruit_SPIDevice *_spidevice;
|
||||
Adafruit_BusIO_SPIRegType _spiregtype;
|
||||
uint16_t _address;
|
||||
uint8_t _width, _addrwidth, _byteorder;
|
||||
uint8_t _buffer[4]; // we won't support anything larger than uint32 for
|
||||
// non-buffered read
|
||||
uint32_t _cached = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
* @brief The class which defines a slice of bits from within a device register
|
||||
* (a location to read/write data from)
|
||||
*/
|
||||
class Adafruit_BusIO_RegisterBits {
|
||||
public:
|
||||
Adafruit_BusIO_RegisterBits(Adafruit_BusIO_Register *reg, uint8_t bits,
|
||||
uint8_t shift);
|
||||
bool write(uint32_t value);
|
||||
uint32_t read(void);
|
||||
|
||||
private:
|
||||
Adafruit_BusIO_Register *_register;
|
||||
uint8_t _bits, _shift;
|
||||
};
|
||||
|
||||
#endif // SPI exists
|
||||
#endif // BusIO_Register_h
|
313
src/Adafruit_BusIO-master/Adafruit_I2CDevice.cpp
Normal file
313
src/Adafruit_BusIO-master/Adafruit_I2CDevice.cpp
Normal file
@ -0,0 +1,313 @@
|
||||
#include "Adafruit_I2CDevice.h"
|
||||
|
||||
//#define DEBUG_SERIAL Serial
|
||||
|
||||
/*!
|
||||
* @brief Create an I2C device at a given address
|
||||
* @param addr The 7-bit I2C address for the device
|
||||
* @param theWire The I2C bus to use, defaults to &Wire
|
||||
*/
|
||||
Adafruit_I2CDevice::Adafruit_I2CDevice(uint8_t addr, TwoWire *theWire) {
|
||||
_addr = addr;
|
||||
_wire = theWire;
|
||||
_begun = false;
|
||||
#ifdef ARDUINO_ARCH_SAMD
|
||||
_maxBufferSize = 250; // as defined in Wire.h's RingBuffer
|
||||
#elif defined(ESP32)
|
||||
_maxBufferSize = I2C_BUFFER_LENGTH;
|
||||
#else
|
||||
_maxBufferSize = 32;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Initializes and does basic address detection
|
||||
* @param addr_detect Whether we should attempt to detect the I2C address
|
||||
* with a scan. 99% of sensors/devices don't mind but once in a while, they spaz
|
||||
* on a scan!
|
||||
* @return True if I2C initialized and a device with the addr found
|
||||
*/
|
||||
bool Adafruit_I2CDevice::begin(bool addr_detect) {
|
||||
_wire->begin();
|
||||
_begun = true;
|
||||
|
||||
if (addr_detect) {
|
||||
return detected();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief De-initialize device, turn off the Wire interface
|
||||
*/
|
||||
void Adafruit_I2CDevice::end(void) {
|
||||
// Not all port implement Wire::end(), such as
|
||||
// - ESP8266
|
||||
// - AVR core without WIRE_HAS_END
|
||||
// - ESP32: end() is implemented since 2.0.1 which is latest at the moment.
|
||||
// Temporarily disable for now to give time for user to update.
|
||||
#if !(defined(ESP8266) || \
|
||||
(defined(ARDUINO_ARCH_AVR) && !defined(WIRE_HAS_END)) || \
|
||||
defined(ARDUINO_ARCH_ESP32))
|
||||
_wire->end();
|
||||
_begun = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Scans I2C for the address - note will give a false-positive
|
||||
* if there's no pullups on I2C
|
||||
* @return True if I2C initialized and a device with the addr found
|
||||
*/
|
||||
bool Adafruit_I2CDevice::detected(void) {
|
||||
// Init I2C if not done yet
|
||||
if (!_begun && !begin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// A basic scanner, see if it ACK's
|
||||
_wire->beginTransmission(_addr);
|
||||
if (_wire->endTransmission() == 0) {
|
||||
#ifdef DEBUG_SERIAL
|
||||
DEBUG_SERIAL.println(F("Detected"));
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
#ifdef DEBUG_SERIAL
|
||||
DEBUG_SERIAL.println(F("Not detected"));
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Write a buffer or two to the I2C device. Cannot be more than
|
||||
* maxBufferSize() bytes.
|
||||
* @param buffer Pointer to buffer of data to write. This is const to
|
||||
* ensure the content of this buffer doesn't change.
|
||||
* @param len Number of bytes from buffer to write
|
||||
* @param prefix_buffer Pointer to optional array of data to write before
|
||||
* buffer. Cannot be more than maxBufferSize() bytes. This is const to
|
||||
* ensure the content of this buffer doesn't change.
|
||||
* @param prefix_len Number of bytes from prefix buffer to write
|
||||
* @param stop Whether to send an I2C STOP signal on write
|
||||
* @return True if write was successful, otherwise false.
|
||||
*/
|
||||
bool Adafruit_I2CDevice::write(const uint8_t *buffer, size_t len, bool stop,
|
||||
const uint8_t *prefix_buffer,
|
||||
size_t prefix_len) {
|
||||
if ((len + prefix_len) > maxBufferSize()) {
|
||||
// currently not guaranteed to work if more than 32 bytes!
|
||||
// we will need to find out if some platforms have larger
|
||||
// I2C buffer sizes :/
|
||||
#ifdef DEBUG_SERIAL
|
||||
DEBUG_SERIAL.println(F("\tI2CDevice could not write such a large buffer"));
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
_wire->beginTransmission(_addr);
|
||||
|
||||
// Write the prefix data (usually an address)
|
||||
if ((prefix_len != 0) && (prefix_buffer != nullptr)) {
|
||||
if (_wire->write(prefix_buffer, prefix_len) != prefix_len) {
|
||||
#ifdef DEBUG_SERIAL
|
||||
DEBUG_SERIAL.println(F("\tI2CDevice failed to write"));
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Write the data itself
|
||||
if (_wire->write(buffer, len) != len) {
|
||||
#ifdef DEBUG_SERIAL
|
||||
DEBUG_SERIAL.println(F("\tI2CDevice failed to write"));
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_SERIAL
|
||||
|
||||
DEBUG_SERIAL.print(F("\tI2CWRITE @ 0x"));
|
||||
DEBUG_SERIAL.print(_addr, HEX);
|
||||
DEBUG_SERIAL.print(F(" :: "));
|
||||
if ((prefix_len != 0) && (prefix_buffer != nullptr)) {
|
||||
for (uint16_t i = 0; i < prefix_len; i++) {
|
||||
DEBUG_SERIAL.print(F("0x"));
|
||||
DEBUG_SERIAL.print(prefix_buffer[i], HEX);
|
||||
DEBUG_SERIAL.print(F(", "));
|
||||
}
|
||||
}
|
||||
for (uint16_t i = 0; i < len; i++) {
|
||||
DEBUG_SERIAL.print(F("0x"));
|
||||
DEBUG_SERIAL.print(buffer[i], HEX);
|
||||
DEBUG_SERIAL.print(F(", "));
|
||||
if (i % 32 == 31) {
|
||||
DEBUG_SERIAL.println();
|
||||
}
|
||||
}
|
||||
|
||||
if (stop) {
|
||||
DEBUG_SERIAL.print("\tSTOP");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_wire->endTransmission(stop) == 0) {
|
||||
#ifdef DEBUG_SERIAL
|
||||
DEBUG_SERIAL.println();
|
||||
// DEBUG_SERIAL.println("Sent!");
|
||||
#endif
|
||||
return true;
|
||||
} else {
|
||||
#ifdef DEBUG_SERIAL
|
||||
DEBUG_SERIAL.println("\tFailed to send!");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Read from I2C into a buffer from the I2C device.
|
||||
* Cannot be more than maxBufferSize() bytes.
|
||||
* @param buffer Pointer to buffer of data to read into
|
||||
* @param len Number of bytes from buffer to read.
|
||||
* @param stop Whether to send an I2C STOP signal on read
|
||||
* @return True if read was successful, otherwise false.
|
||||
*/
|
||||
bool Adafruit_I2CDevice::read(uint8_t *buffer, size_t len, bool stop) {
|
||||
size_t pos = 0;
|
||||
while (pos < len) {
|
||||
size_t read_len =
|
||||
((len - pos) > maxBufferSize()) ? maxBufferSize() : (len - pos);
|
||||
bool read_stop = (pos < (len - read_len)) ? false : stop;
|
||||
if (!_read(buffer + pos, read_len, read_stop))
|
||||
return false;
|
||||
pos += read_len;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Adafruit_I2CDevice::_read(uint8_t *buffer, size_t len, bool stop) {
|
||||
#if defined(TinyWireM_h)
|
||||
size_t recv = _wire->requestFrom((uint8_t)_addr, (uint8_t)len);
|
||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||
size_t recv = _wire->requestFrom(_addr, len, stop);
|
||||
#else
|
||||
size_t recv = _wire->requestFrom((uint8_t)_addr, (uint8_t)len, (uint8_t)stop);
|
||||
#endif
|
||||
|
||||
if (recv != len) {
|
||||
// Not enough data available to fulfill our obligation!
|
||||
#ifdef DEBUG_SERIAL
|
||||
DEBUG_SERIAL.print(F("\tI2CDevice did not receive enough data: "));
|
||||
DEBUG_SERIAL.println(recv);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < len; i++) {
|
||||
buffer[i] = _wire->read();
|
||||
}
|
||||
|
||||
#ifdef DEBUG_SERIAL
|
||||
DEBUG_SERIAL.print(F("\tI2CREAD @ 0x"));
|
||||
DEBUG_SERIAL.print(_addr, HEX);
|
||||
DEBUG_SERIAL.print(F(" :: "));
|
||||
for (uint16_t i = 0; i < len; i++) {
|
||||
DEBUG_SERIAL.print(F("0x"));
|
||||
DEBUG_SERIAL.print(buffer[i], HEX);
|
||||
DEBUG_SERIAL.print(F(", "));
|
||||
if (len % 32 == 31) {
|
||||
DEBUG_SERIAL.println();
|
||||
}
|
||||
}
|
||||
DEBUG_SERIAL.println();
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Write some data, then read some data from I2C into another buffer.
|
||||
* Cannot be more than maxBufferSize() bytes. The buffers can point to
|
||||
* same/overlapping locations.
|
||||
* @param write_buffer Pointer to buffer of data to write from
|
||||
* @param write_len Number of bytes from buffer to write.
|
||||
* @param read_buffer Pointer to buffer of data to read into.
|
||||
* @param read_len Number of bytes from buffer to read.
|
||||
* @param stop Whether to send an I2C STOP signal between the write and read
|
||||
* @return True if write & read was successful, otherwise false.
|
||||
*/
|
||||
bool Adafruit_I2CDevice::write_then_read(const uint8_t *write_buffer,
|
||||
size_t write_len, uint8_t *read_buffer,
|
||||
size_t read_len, bool stop) {
|
||||
if (!write(write_buffer, write_len, stop)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return read(read_buffer, read_len);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Returns the 7-bit address of this device
|
||||
* @return The 7-bit address of this device
|
||||
*/
|
||||
uint8_t Adafruit_I2CDevice::address(void) { return _addr; }
|
||||
|
||||
/*!
|
||||
* @brief Change the I2C clock speed to desired (relies on
|
||||
* underlying Wire support!
|
||||
* @param desiredclk The desired I2C SCL frequency
|
||||
* @return True if this platform supports changing I2C speed.
|
||||
* Not necessarily that the speed was achieved!
|
||||
*/
|
||||
bool Adafruit_I2CDevice::setSpeed(uint32_t desiredclk) {
|
||||
#if defined(__AVR_ATmega328__) || \
|
||||
defined(__AVR_ATmega328P__) // fix arduino core set clock
|
||||
// calculate TWBR correctly
|
||||
|
||||
if ((F_CPU / 18) < desiredclk) {
|
||||
#ifdef DEBUG_SERIAL
|
||||
Serial.println(F("I2C.setSpeed too high."));
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
uint32_t atwbr = ((F_CPU / desiredclk) - 16) / 2;
|
||||
if (atwbr > 16320) {
|
||||
#ifdef DEBUG_SERIAL
|
||||
Serial.println(F("I2C.setSpeed too low."));
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
if (atwbr <= 255) {
|
||||
atwbr /= 1;
|
||||
TWSR = 0x0;
|
||||
} else if (atwbr <= 1020) {
|
||||
atwbr /= 4;
|
||||
TWSR = 0x1;
|
||||
} else if (atwbr <= 4080) {
|
||||
atwbr /= 16;
|
||||
TWSR = 0x2;
|
||||
} else { // if (atwbr <= 16320)
|
||||
atwbr /= 64;
|
||||
TWSR = 0x3;
|
||||
}
|
||||
TWBR = atwbr;
|
||||
|
||||
#ifdef DEBUG_SERIAL
|
||||
Serial.print(F("TWSR prescaler = "));
|
||||
Serial.println(pow(4, TWSR));
|
||||
Serial.print(F("TWBR = "));
|
||||
Serial.println(atwbr);
|
||||
#endif
|
||||
return true;
|
||||
#elif (ARDUINO >= 157) && !defined(ARDUINO_STM32_FEATHER) && \
|
||||
!defined(TinyWireM_h)
|
||||
_wire->setClock(desiredclk);
|
||||
return true;
|
||||
|
||||
#else
|
||||
(void)desiredclk;
|
||||
return false;
|
||||
#endif
|
||||
}
|
36
src/Adafruit_BusIO-master/Adafruit_I2CDevice.h
Normal file
36
src/Adafruit_BusIO-master/Adafruit_I2CDevice.h
Normal file
@ -0,0 +1,36 @@
|
||||
#ifndef Adafruit_I2CDevice_h
|
||||
#define Adafruit_I2CDevice_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
///< The class which defines how we will talk to this device over I2C
|
||||
class Adafruit_I2CDevice {
|
||||
public:
|
||||
Adafruit_I2CDevice(uint8_t addr, TwoWire *theWire = &Wire);
|
||||
uint8_t address(void);
|
||||
bool begin(bool addr_detect = true);
|
||||
void end(void);
|
||||
bool detected(void);
|
||||
|
||||
bool read(uint8_t *buffer, size_t len, bool stop = true);
|
||||
bool write(const uint8_t *buffer, size_t len, bool stop = true,
|
||||
const uint8_t *prefix_buffer = nullptr, size_t prefix_len = 0);
|
||||
bool write_then_read(const uint8_t *write_buffer, size_t write_len,
|
||||
uint8_t *read_buffer, size_t read_len,
|
||||
bool stop = false);
|
||||
bool setSpeed(uint32_t desiredclk);
|
||||
|
||||
/*! @brief How many bytes we can read in a transaction
|
||||
* @return The size of the Wire receive/transmit buffer */
|
||||
size_t maxBufferSize() { return _maxBufferSize; }
|
||||
|
||||
private:
|
||||
uint8_t _addr;
|
||||
TwoWire *_wire;
|
||||
bool _begun;
|
||||
size_t _maxBufferSize;
|
||||
bool _read(uint8_t *buffer, size_t len, bool stop);
|
||||
};
|
||||
|
||||
#endif // Adafruit_I2CDevice_h
|
10
src/Adafruit_BusIO-master/Adafruit_I2CRegister.h
Normal file
10
src/Adafruit_BusIO-master/Adafruit_I2CRegister.h
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef _ADAFRUIT_I2C_REGISTER_H_
|
||||
#define _ADAFRUIT_I2C_REGISTER_H_
|
||||
|
||||
#include "Adafruit_BusIO_Register.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
typedef Adafruit_BusIO_Register Adafruit_I2CRegister;
|
||||
typedef Adafruit_BusIO_RegisterBits Adafruit_I2CRegisterBits;
|
||||
|
||||
#endif
|
493
src/Adafruit_BusIO-master/Adafruit_SPIDevice.cpp
Normal file
493
src/Adafruit_BusIO-master/Adafruit_SPIDevice.cpp
Normal file
@ -0,0 +1,493 @@
|
||||
#include "Adafruit_SPIDevice.h"
|
||||
|
||||
#if !defined(SPI_INTERFACES_COUNT) || \
|
||||
(defined(SPI_INTERFACES_COUNT) && (SPI_INTERFACES_COUNT > 0))
|
||||
|
||||
//#define DEBUG_SERIAL Serial
|
||||
|
||||
/*!
|
||||
* @brief Create an SPI device with the given CS pin and settings
|
||||
* @param cspin The arduino pin number to use for chip select
|
||||
* @param freq The SPI clock frequency to use, defaults to 1MHz
|
||||
* @param dataOrder The SPI data order to use for bits within each byte,
|
||||
* defaults to SPI_BITORDER_MSBFIRST
|
||||
* @param dataMode The SPI mode to use, defaults to SPI_MODE0
|
||||
* @param theSPI The SPI bus to use, defaults to &theSPI
|
||||
*/
|
||||
Adafruit_SPIDevice::Adafruit_SPIDevice(int8_t cspin, uint32_t freq,
|
||||
BusIOBitOrder dataOrder,
|
||||
uint8_t dataMode, SPIClass *theSPI) {
|
||||
_cs = cspin;
|
||||
_sck = _mosi = _miso = -1;
|
||||
_spi = theSPI;
|
||||
_begun = false;
|
||||
_spiSetting = new SPISettings(freq, dataOrder, dataMode);
|
||||
_freq = freq;
|
||||
_dataOrder = dataOrder;
|
||||
_dataMode = dataMode;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Create an SPI device with the given CS pin and settings
|
||||
* @param cspin The arduino pin number to use for chip select
|
||||
* @param sckpin The arduino pin number to use for SCK
|
||||
* @param misopin The arduino pin number to use for MISO, set to -1 if not
|
||||
* used
|
||||
* @param mosipin The arduino pin number to use for MOSI, set to -1 if not
|
||||
* used
|
||||
* @param freq The SPI clock frequency to use, defaults to 1MHz
|
||||
* @param dataOrder The SPI data order to use for bits within each byte,
|
||||
* defaults to SPI_BITORDER_MSBFIRST
|
||||
* @param dataMode The SPI mode to use, defaults to SPI_MODE0
|
||||
*/
|
||||
Adafruit_SPIDevice::Adafruit_SPIDevice(int8_t cspin, int8_t sckpin,
|
||||
int8_t misopin, int8_t mosipin,
|
||||
uint32_t freq, BusIOBitOrder dataOrder,
|
||||
uint8_t dataMode) {
|
||||
_cs = cspin;
|
||||
_sck = sckpin;
|
||||
_miso = misopin;
|
||||
_mosi = mosipin;
|
||||
|
||||
#ifdef BUSIO_USE_FAST_PINIO
|
||||
csPort = (BusIO_PortReg *)portOutputRegister(digitalPinToPort(cspin));
|
||||
csPinMask = digitalPinToBitMask(cspin);
|
||||
if (mosipin != -1) {
|
||||
mosiPort = (BusIO_PortReg *)portOutputRegister(digitalPinToPort(mosipin));
|
||||
mosiPinMask = digitalPinToBitMask(mosipin);
|
||||
}
|
||||
if (misopin != -1) {
|
||||
misoPort = (BusIO_PortReg *)portInputRegister(digitalPinToPort(misopin));
|
||||
misoPinMask = digitalPinToBitMask(misopin);
|
||||
}
|
||||
clkPort = (BusIO_PortReg *)portOutputRegister(digitalPinToPort(sckpin));
|
||||
clkPinMask = digitalPinToBitMask(sckpin);
|
||||
#endif
|
||||
|
||||
_freq = freq;
|
||||
_dataOrder = dataOrder;
|
||||
_dataMode = dataMode;
|
||||
_begun = false;
|
||||
_spiSetting = new SPISettings(freq, dataOrder, dataMode);
|
||||
_spi = nullptr;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Release memory allocated in constructors
|
||||
*/
|
||||
Adafruit_SPIDevice::~Adafruit_SPIDevice() { delete _spiSetting; }
|
||||
|
||||
/*!
|
||||
* @brief Initializes SPI bus and sets CS pin high
|
||||
* @return Always returns true because there's no way to test success of SPI
|
||||
* init
|
||||
*/
|
||||
bool Adafruit_SPIDevice::begin(void) {
|
||||
if (_cs != -1) {
|
||||
pinMode(_cs, OUTPUT);
|
||||
digitalWrite(_cs, HIGH);
|
||||
}
|
||||
|
||||
if (_spi) { // hardware SPI
|
||||
_spi->begin();
|
||||
} else {
|
||||
pinMode(_sck, OUTPUT);
|
||||
|
||||
if ((_dataMode == SPI_MODE0) || (_dataMode == SPI_MODE1)) {
|
||||
// idle low on mode 0 and 1
|
||||
digitalWrite(_sck, LOW);
|
||||
} else {
|
||||
// idle high on mode 2 or 3
|
||||
digitalWrite(_sck, HIGH);
|
||||
}
|
||||
if (_mosi != -1) {
|
||||
pinMode(_mosi, OUTPUT);
|
||||
digitalWrite(_mosi, HIGH);
|
||||
}
|
||||
if (_miso != -1) {
|
||||
pinMode(_miso, INPUT);
|
||||
}
|
||||
}
|
||||
|
||||
_begun = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Transfer (send/receive) a buffer over hard/soft SPI, without
|
||||
* transaction management
|
||||
* @param buffer The buffer to send and receive at the same time
|
||||
* @param len The number of bytes to transfer
|
||||
*/
|
||||
void Adafruit_SPIDevice::transfer(uint8_t *buffer, size_t len) {
|
||||
if (_spi) {
|
||||
// hardware SPI is easy
|
||||
|
||||
#if defined(SPARK)
|
||||
_spi->transfer(buffer, buffer, len, nullptr);
|
||||
#elif defined(STM32)
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
_spi->transfer(buffer[i]);
|
||||
}
|
||||
#else
|
||||
_spi->transfer(buffer, len);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t startbit;
|
||||
if (_dataOrder == SPI_BITORDER_LSBFIRST) {
|
||||
startbit = 0x1;
|
||||
} else {
|
||||
startbit = 0x80;
|
||||
}
|
||||
|
||||
bool towrite, lastmosi = !(buffer[0] & startbit);
|
||||
uint8_t bitdelay_us = (1000000 / _freq) / 2;
|
||||
|
||||
// for softSPI we'll do it by hand
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
// software SPI
|
||||
uint8_t reply = 0;
|
||||
uint8_t send = buffer[i];
|
||||
|
||||
/*
|
||||
Serial.print("\tSending software SPI byte 0x");
|
||||
Serial.print(send, HEX);
|
||||
Serial.print(" -> 0x");
|
||||
*/
|
||||
|
||||
// Serial.print(send, HEX);
|
||||
for (uint8_t b = startbit; b != 0;
|
||||
b = (_dataOrder == SPI_BITORDER_LSBFIRST) ? b << 1 : b >> 1) {
|
||||
|
||||
if (bitdelay_us) {
|
||||
delayMicroseconds(bitdelay_us);
|
||||
}
|
||||
|
||||
if (_dataMode == SPI_MODE0 || _dataMode == SPI_MODE2) {
|
||||
towrite = send & b;
|
||||
if ((_mosi != -1) && (lastmosi != towrite)) {
|
||||
#ifdef BUSIO_USE_FAST_PINIO
|
||||
if (towrite)
|
||||
*mosiPort |= mosiPinMask;
|
||||
else
|
||||
*mosiPort &= ~mosiPinMask;
|
||||
#else
|
||||
digitalWrite(_mosi, towrite);
|
||||
#endif
|
||||
lastmosi = towrite;
|
||||
}
|
||||
|
||||
#ifdef BUSIO_USE_FAST_PINIO
|
||||
*clkPort |= clkPinMask; // Clock high
|
||||
#else
|
||||
digitalWrite(_sck, HIGH);
|
||||
#endif
|
||||
|
||||
if (bitdelay_us) {
|
||||
delayMicroseconds(bitdelay_us);
|
||||
}
|
||||
|
||||
if (_miso != -1) {
|
||||
#ifdef BUSIO_USE_FAST_PINIO
|
||||
if (*misoPort & misoPinMask) {
|
||||
#else
|
||||
if (digitalRead(_miso)) {
|
||||
#endif
|
||||
reply |= b;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef BUSIO_USE_FAST_PINIO
|
||||
*clkPort &= ~clkPinMask; // Clock low
|
||||
#else
|
||||
digitalWrite(_sck, LOW);
|
||||
#endif
|
||||
} else { // if (_dataMode == SPI_MODE1 || _dataMode == SPI_MODE3)
|
||||
|
||||
#ifdef BUSIO_USE_FAST_PINIO
|
||||
*clkPort |= clkPinMask; // Clock high
|
||||
#else
|
||||
digitalWrite(_sck, HIGH);
|
||||
#endif
|
||||
|
||||
if (bitdelay_us) {
|
||||
delayMicroseconds(bitdelay_us);
|
||||
}
|
||||
|
||||
if (_mosi != -1) {
|
||||
#ifdef BUSIO_USE_FAST_PINIO
|
||||
if (send & b)
|
||||
*mosiPort |= mosiPinMask;
|
||||
else
|
||||
*mosiPort &= ~mosiPinMask;
|
||||
#else
|
||||
digitalWrite(_mosi, send & b);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef BUSIO_USE_FAST_PINIO
|
||||
*clkPort &= ~clkPinMask; // Clock low
|
||||
#else
|
||||
digitalWrite(_sck, LOW);
|
||||
#endif
|
||||
|
||||
if (_miso != -1) {
|
||||
#ifdef BUSIO_USE_FAST_PINIO
|
||||
if (*misoPort & misoPinMask) {
|
||||
#else
|
||||
if (digitalRead(_miso)) {
|
||||
#endif
|
||||
reply |= b;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_miso != -1) {
|
||||
buffer[i] = reply;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Transfer (send/receive) one byte over hard/soft SPI, without
|
||||
* transaction management
|
||||
* @param send The byte to send
|
||||
* @return The byte received while transmitting
|
||||
*/
|
||||
uint8_t Adafruit_SPIDevice::transfer(uint8_t send) {
|
||||
uint8_t data = send;
|
||||
transfer(&data, 1);
|
||||
return data;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Manually begin a transaction (calls beginTransaction if hardware
|
||||
* SPI)
|
||||
*/
|
||||
void Adafruit_SPIDevice::beginTransaction(void) {
|
||||
if (_spi) {
|
||||
_spi->beginTransaction(*_spiSetting);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Manually end a transaction (calls endTransaction if hardware SPI)
|
||||
*/
|
||||
void Adafruit_SPIDevice::endTransaction(void) {
|
||||
if (_spi) {
|
||||
_spi->endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Assert/Deassert the CS pin if it is defined
|
||||
* @param value The state the CS is set to
|
||||
*/
|
||||
void Adafruit_SPIDevice::setChipSelect(int value) {
|
||||
if (_cs != -1) {
|
||||
digitalWrite(_cs, value);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Write a buffer or two to the SPI device, with transaction
|
||||
* management.
|
||||
* @brief Manually begin a transaction (calls beginTransaction if hardware
|
||||
* SPI) with asserting the CS pin
|
||||
*/
|
||||
void Adafruit_SPIDevice::beginTransactionWithAssertingCS() {
|
||||
beginTransaction();
|
||||
setChipSelect(LOW);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Manually end a transaction (calls endTransaction if hardware SPI)
|
||||
* with deasserting the CS pin
|
||||
*/
|
||||
void Adafruit_SPIDevice::endTransactionWithDeassertingCS() {
|
||||
setChipSelect(HIGH);
|
||||
endTransaction();
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Write a buffer or two to the SPI device, with transaction
|
||||
* management.
|
||||
* @param buffer Pointer to buffer of data to write
|
||||
* @param len Number of bytes from buffer to write
|
||||
* @param prefix_buffer Pointer to optional array of data to write before
|
||||
* buffer.
|
||||
* @param prefix_len Number of bytes from prefix buffer to write
|
||||
* @return Always returns true because there's no way to test success of SPI
|
||||
* writes
|
||||
*/
|
||||
bool Adafruit_SPIDevice::write(const uint8_t *buffer, size_t len,
|
||||
const uint8_t *prefix_buffer,
|
||||
size_t prefix_len) {
|
||||
beginTransactionWithAssertingCS();
|
||||
|
||||
// do the writing
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
if (_spi) {
|
||||
if (prefix_len > 0) {
|
||||
_spi->transferBytes(prefix_buffer, nullptr, prefix_len);
|
||||
}
|
||||
if (len > 0) {
|
||||
_spi->transferBytes(buffer, nullptr, len);
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
for (size_t i = 0; i < prefix_len; i++) {
|
||||
transfer(prefix_buffer[i]);
|
||||
}
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
transfer(buffer[i]);
|
||||
}
|
||||
}
|
||||
endTransactionWithDeassertingCS();
|
||||
|
||||
#ifdef DEBUG_SERIAL
|
||||
DEBUG_SERIAL.print(F("\tSPIDevice Wrote: "));
|
||||
if ((prefix_len != 0) && (prefix_buffer != nullptr)) {
|
||||
for (uint16_t i = 0; i < prefix_len; i++) {
|
||||
DEBUG_SERIAL.print(F("0x"));
|
||||
DEBUG_SERIAL.print(prefix_buffer[i], HEX);
|
||||
DEBUG_SERIAL.print(F(", "));
|
||||
}
|
||||
}
|
||||
for (uint16_t i = 0; i < len; i++) {
|
||||
DEBUG_SERIAL.print(F("0x"));
|
||||
DEBUG_SERIAL.print(buffer[i], HEX);
|
||||
DEBUG_SERIAL.print(F(", "));
|
||||
if (i % 32 == 31) {
|
||||
DEBUG_SERIAL.println();
|
||||
}
|
||||
}
|
||||
DEBUG_SERIAL.println();
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Read from SPI into a buffer from the SPI device, with transaction
|
||||
* management.
|
||||
* @param buffer Pointer to buffer of data to read into
|
||||
* @param len Number of bytes from buffer to read.
|
||||
* @param sendvalue The 8-bits of data to write when doing the data read,
|
||||
* defaults to 0xFF
|
||||
* @return Always returns true because there's no way to test success of SPI
|
||||
* writes
|
||||
*/
|
||||
bool Adafruit_SPIDevice::read(uint8_t *buffer, size_t len, uint8_t sendvalue) {
|
||||
memset(buffer, sendvalue, len); // clear out existing buffer
|
||||
|
||||
beginTransactionWithAssertingCS();
|
||||
transfer(buffer, len);
|
||||
endTransactionWithDeassertingCS();
|
||||
|
||||
#ifdef DEBUG_SERIAL
|
||||
DEBUG_SERIAL.print(F("\tSPIDevice Read: "));
|
||||
for (uint16_t i = 0; i < len; i++) {
|
||||
DEBUG_SERIAL.print(F("0x"));
|
||||
DEBUG_SERIAL.print(buffer[i], HEX);
|
||||
DEBUG_SERIAL.print(F(", "));
|
||||
if (len % 32 == 31) {
|
||||
DEBUG_SERIAL.println();
|
||||
}
|
||||
}
|
||||
DEBUG_SERIAL.println();
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Write some data, then read some data from SPI into another buffer,
|
||||
* with transaction management. The buffers can point to same/overlapping
|
||||
* locations. This does not transmit-receive at the same time!
|
||||
* @param write_buffer Pointer to buffer of data to write from
|
||||
* @param write_len Number of bytes from buffer to write.
|
||||
* @param read_buffer Pointer to buffer of data to read into.
|
||||
* @param read_len Number of bytes from buffer to read.
|
||||
* @param sendvalue The 8-bits of data to write when doing the data read,
|
||||
* defaults to 0xFF
|
||||
* @return Always returns true because there's no way to test success of SPI
|
||||
* writes
|
||||
*/
|
||||
bool Adafruit_SPIDevice::write_then_read(const uint8_t *write_buffer,
|
||||
size_t write_len, uint8_t *read_buffer,
|
||||
size_t read_len, uint8_t sendvalue) {
|
||||
beginTransactionWithAssertingCS();
|
||||
// do the writing
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
if (_spi) {
|
||||
if (write_len > 0) {
|
||||
_spi->transferBytes(write_buffer, nullptr, write_len);
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
for (size_t i = 0; i < write_len; i++) {
|
||||
transfer(write_buffer[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_SERIAL
|
||||
DEBUG_SERIAL.print(F("\tSPIDevice Wrote: "));
|
||||
for (uint16_t i = 0; i < write_len; i++) {
|
||||
DEBUG_SERIAL.print(F("0x"));
|
||||
DEBUG_SERIAL.print(write_buffer[i], HEX);
|
||||
DEBUG_SERIAL.print(F(", "));
|
||||
if (write_len % 32 == 31) {
|
||||
DEBUG_SERIAL.println();
|
||||
}
|
||||
}
|
||||
DEBUG_SERIAL.println();
|
||||
#endif
|
||||
|
||||
// do the reading
|
||||
for (size_t i = 0; i < read_len; i++) {
|
||||
read_buffer[i] = transfer(sendvalue);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_SERIAL
|
||||
DEBUG_SERIAL.print(F("\tSPIDevice Read: "));
|
||||
for (uint16_t i = 0; i < read_len; i++) {
|
||||
DEBUG_SERIAL.print(F("0x"));
|
||||
DEBUG_SERIAL.print(read_buffer[i], HEX);
|
||||
DEBUG_SERIAL.print(F(", "));
|
||||
if (read_len % 32 == 31) {
|
||||
DEBUG_SERIAL.println();
|
||||
}
|
||||
}
|
||||
DEBUG_SERIAL.println();
|
||||
#endif
|
||||
|
||||
endTransactionWithDeassertingCS();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Write some data and read some data at the same time from SPI
|
||||
* into the same buffer, with transaction management. This is basicaly a wrapper
|
||||
* for transfer() with CS-pin and transaction management. This /does/
|
||||
* transmit-receive at the same time!
|
||||
* @param buffer Pointer to buffer of data to write/read to/from
|
||||
* @param len Number of bytes from buffer to write/read.
|
||||
* @return Always returns true because there's no way to test success of SPI
|
||||
* writes
|
||||
*/
|
||||
bool Adafruit_SPIDevice::write_and_read(uint8_t *buffer, size_t len) {
|
||||
beginTransactionWithAssertingCS();
|
||||
transfer(buffer, len);
|
||||
endTransactionWithDeassertingCS();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // SPI exists
|
123
src/Adafruit_BusIO-master/Adafruit_SPIDevice.h
Normal file
123
src/Adafruit_BusIO-master/Adafruit_SPIDevice.h
Normal file
@ -0,0 +1,123 @@
|
||||
#ifndef Adafruit_SPIDevice_h
|
||||
#define Adafruit_SPIDevice_h
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#if !defined(SPI_INTERFACES_COUNT) || \
|
||||
(defined(SPI_INTERFACES_COUNT) && (SPI_INTERFACES_COUNT > 0))
|
||||
|
||||
#include <SPI.h>
|
||||
|
||||
// some modern SPI definitions don't have BitOrder enum
|
||||
#if (defined(__AVR__) && !defined(ARDUINO_ARCH_MEGAAVR)) || \
|
||||
defined(ESP8266) || defined(TEENSYDUINO) || defined(SPARK) || \
|
||||
defined(ARDUINO_ARCH_SPRESENSE) || defined(MEGATINYCORE) || \
|
||||
defined(DXCORE) || defined(ARDUINO_AVR_ATmega4809) || \
|
||||
defined(ARDUINO_AVR_ATmega4808) || defined(ARDUINO_AVR_ATmega3209) || \
|
||||
defined(ARDUINO_AVR_ATmega3208) || defined(ARDUINO_AVR_ATmega1609) || \
|
||||
defined(ARDUINO_AVR_ATmega1608) || defined(ARDUINO_AVR_ATmega809) || \
|
||||
defined(ARDUINO_AVR_ATmega808) || defined(ARDUINO_ARCH_ARC32)
|
||||
|
||||
typedef enum _BitOrder {
|
||||
SPI_BITORDER_MSBFIRST = MSBFIRST,
|
||||
SPI_BITORDER_LSBFIRST = LSBFIRST,
|
||||
} BusIOBitOrder;
|
||||
|
||||
#elif defined(ESP32) || defined(__ASR6501__) || defined(__ASR6502__)
|
||||
|
||||
// some modern SPI definitions don't have BitOrder enum and have different SPI
|
||||
// mode defines
|
||||
typedef enum _BitOrder {
|
||||
SPI_BITORDER_MSBFIRST = SPI_MSBFIRST,
|
||||
SPI_BITORDER_LSBFIRST = SPI_LSBFIRST,
|
||||
} BusIOBitOrder;
|
||||
|
||||
#else
|
||||
// Some platforms have a BitOrder enum but its named MSBFIRST/LSBFIRST
|
||||
#define SPI_BITORDER_MSBFIRST MSBFIRST
|
||||
#define SPI_BITORDER_LSBFIRST LSBFIRST
|
||||
typedef BitOrder BusIOBitOrder;
|
||||
#endif
|
||||
|
||||
#if defined(__IMXRT1062__) // Teensy 4.x
|
||||
// *Warning* I disabled the usage of FAST_PINIO as the set/clear operations
|
||||
// used in the cpp file are not atomic and can effect multiple IO pins
|
||||
// and if an interrupt happens in between the time the code reads the register
|
||||
// and writes out the updated value, that changes one or more other IO pins
|
||||
// on that same IO port, those change will be clobbered when the updated
|
||||
// values are written back. A fast version can be implemented that uses the
|
||||
// ports set and clear registers which are atomic.
|
||||
// typedef volatile uint32_t BusIO_PortReg;
|
||||
// typedef uint32_t BusIO_PortMask;
|
||||
//#define BUSIO_USE_FAST_PINIO
|
||||
|
||||
#elif defined(__AVR__) || defined(TEENSYDUINO)
|
||||
typedef volatile uint8_t BusIO_PortReg;
|
||||
typedef uint8_t BusIO_PortMask;
|
||||
#define BUSIO_USE_FAST_PINIO
|
||||
|
||||
#elif defined(ESP8266) || defined(ESP32) || defined(__SAM3X8E__) || \
|
||||
defined(ARDUINO_ARCH_SAMD)
|
||||
typedef volatile uint32_t BusIO_PortReg;
|
||||
typedef uint32_t BusIO_PortMask;
|
||||
#define BUSIO_USE_FAST_PINIO
|
||||
|
||||
#elif (defined(__arm__) || defined(ARDUINO_FEATHER52)) && \
|
||||
!defined(ARDUINO_ARCH_MBED) && !defined(ARDUINO_ARCH_RP2040)
|
||||
typedef volatile uint32_t BusIO_PortReg;
|
||||
typedef uint32_t BusIO_PortMask;
|
||||
#if !defined(__ASR6501__) && !defined(__ASR6502__)
|
||||
#define BUSIO_USE_FAST_PINIO
|
||||
#endif
|
||||
|
||||
#else
|
||||
#undef BUSIO_USE_FAST_PINIO
|
||||
#endif
|
||||
|
||||
/**! The class which defines how we will talk to this device over SPI **/
|
||||
class Adafruit_SPIDevice {
|
||||
public:
|
||||
Adafruit_SPIDevice(int8_t cspin, uint32_t freq = 1000000,
|
||||
BusIOBitOrder dataOrder = SPI_BITORDER_MSBFIRST,
|
||||
uint8_t dataMode = SPI_MODE0, SPIClass *theSPI = &SPI);
|
||||
|
||||
Adafruit_SPIDevice(int8_t cspin, int8_t sck, int8_t miso, int8_t mosi,
|
||||
uint32_t freq = 1000000,
|
||||
BusIOBitOrder dataOrder = SPI_BITORDER_MSBFIRST,
|
||||
uint8_t dataMode = SPI_MODE0);
|
||||
~Adafruit_SPIDevice();
|
||||
|
||||
bool begin(void);
|
||||
bool read(uint8_t *buffer, size_t len, uint8_t sendvalue = 0xFF);
|
||||
bool write(const uint8_t *buffer, size_t len,
|
||||
const uint8_t *prefix_buffer = nullptr, size_t prefix_len = 0);
|
||||
bool write_then_read(const uint8_t *write_buffer, size_t write_len,
|
||||
uint8_t *read_buffer, size_t read_len,
|
||||
uint8_t sendvalue = 0xFF);
|
||||
bool write_and_read(uint8_t *buffer, size_t len);
|
||||
|
||||
uint8_t transfer(uint8_t send);
|
||||
void transfer(uint8_t *buffer, size_t len);
|
||||
void beginTransaction(void);
|
||||
void endTransaction(void);
|
||||
void beginTransactionWithAssertingCS();
|
||||
void endTransactionWithDeassertingCS();
|
||||
|
||||
private:
|
||||
SPIClass *_spi;
|
||||
SPISettings *_spiSetting;
|
||||
uint32_t _freq;
|
||||
BusIOBitOrder _dataOrder;
|
||||
uint8_t _dataMode;
|
||||
void setChipSelect(int value);
|
||||
|
||||
int8_t _cs, _sck, _mosi, _miso;
|
||||
#ifdef BUSIO_USE_FAST_PINIO
|
||||
BusIO_PortReg *mosiPort, *clkPort, *misoPort, *csPort;
|
||||
BusIO_PortMask mosiPinMask, misoPinMask, clkPinMask, csPinMask;
|
||||
#endif
|
||||
bool _begun;
|
||||
};
|
||||
|
||||
#endif // has SPI defined
|
||||
#endif // Adafruit_SPIDevice_h
|
400
src/Adafruit_Si4713.cpp
Normal file
400
src/Adafruit_Si4713.cpp
Normal file
@ -0,0 +1,400 @@
|
||||
/*!
|
||||
* @file Adafruit_Si4713.cpp
|
||||
*
|
||||
* @mainpage Adafruit Si4713 breakout
|
||||
*
|
||||
* @section intro_sec Introduction
|
||||
*
|
||||
* I2C Driver for Si4713 breakout
|
||||
*
|
||||
* This is a library for the Adafruit Si4713 breakout:
|
||||
* http://www.adafruit.com/products/1958
|
||||
*
|
||||
* Adafruit invests time and resources providing this open source code,
|
||||
* please support Adafruit and open-source hardware by purchasing products from
|
||||
* Adafruit!
|
||||
*
|
||||
* @section author Author
|
||||
*
|
||||
* Limor Fried/Ladyada (Adafruit Industries).
|
||||
*
|
||||
* @section license License
|
||||
*
|
||||
* BSD (see license.txt)
|
||||
*
|
||||
* @section HISTORY
|
||||
*
|
||||
* v1.0 - First release
|
||||
*/
|
||||
|
||||
#include "Adafruit_Si4713.h"
|
||||
|
||||
/*!
|
||||
* @brief Instantiates a new Si4713 class
|
||||
* @param resetpin
|
||||
* number of pin where reset is connected
|
||||
*
|
||||
*/
|
||||
Adafruit_Si4713::Adafruit_Si4713(int8_t resetpin) { _rst = resetpin; }
|
||||
|
||||
/*!
|
||||
* @brief Setups the i2c and calls powerUp function.
|
||||
* @param addr
|
||||
* i2c address
|
||||
* @param theWire
|
||||
* wire object
|
||||
* @return True if initialization was successful, otherwise false.
|
||||
*
|
||||
*/
|
||||
bool Adafruit_Si4713::begin(uint8_t addr, TwoWire *theWire) {
|
||||
if (i2c_dev)
|
||||
delete i2c_dev;
|
||||
i2c_dev = new Adafruit_I2CDevice(addr, theWire);
|
||||
if (!i2c_dev->begin())
|
||||
return false;
|
||||
|
||||
reset();
|
||||
|
||||
powerUp();
|
||||
|
||||
// check for Si4713
|
||||
if (getRev() != 13)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Resets the registers to default settings and puts chip in
|
||||
* powerdown mode
|
||||
*/
|
||||
void Adafruit_Si4713::reset() {
|
||||
if (_rst > 0) {
|
||||
pinMode(_rst, OUTPUT);
|
||||
digitalWrite(_rst, HIGH);
|
||||
delay(10);
|
||||
digitalWrite(_rst, LOW);
|
||||
delay(10);
|
||||
digitalWrite(_rst, HIGH);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Set chip property over I2C
|
||||
* @param property
|
||||
* prooperty that will be set
|
||||
* @param value
|
||||
* value of property
|
||||
*/
|
||||
void Adafruit_Si4713::setProperty(uint16_t property, uint16_t value) {
|
||||
_i2ccommand[0] = SI4710_CMD_SET_PROPERTY;
|
||||
_i2ccommand[1] = 0;
|
||||
_i2ccommand[2] = property >> 8;
|
||||
_i2ccommand[3] = property & 0xFF;
|
||||
_i2ccommand[4] = value >> 8;
|
||||
_i2ccommand[5] = value & 0xFF;
|
||||
sendCommand(6);
|
||||
|
||||
#ifdef SI4713_CMD_DEBUG
|
||||
Serial.print("Set Prop ");
|
||||
Serial.print(property);
|
||||
Serial.print(" = ");
|
||||
Serial.println(value);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Send command stored in _i2ccommand to chip.
|
||||
* @param len
|
||||
* length of command that will be send
|
||||
*/
|
||||
void Adafruit_Si4713::sendCommand(uint8_t len) {
|
||||
// Send command
|
||||
i2c_dev->write(_i2ccommand, len);
|
||||
// Wait for status CTS bit
|
||||
uint8_t status = 0;
|
||||
while (!(status & SI4710_STATUS_CTS))
|
||||
i2c_dev->read(&status, 1);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Tunes to given transmit frequency.
|
||||
* @param freqKHz
|
||||
* frequency in KHz
|
||||
*/
|
||||
void Adafruit_Si4713::tuneFM(uint16_t freqKHz) {
|
||||
_i2ccommand[0] = SI4710_CMD_TX_TUNE_FREQ;
|
||||
_i2ccommand[1] = 0;
|
||||
_i2ccommand[2] = freqKHz >> 8;
|
||||
_i2ccommand[3] = freqKHz;
|
||||
sendCommand(4);
|
||||
while ((getStatus() & 0x81) != 0x81) {
|
||||
delay(10);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Sets the output power level and tunes the antenna capacitor
|
||||
* @param pwr
|
||||
* power value
|
||||
* @param antcap
|
||||
* antenna capacitor (default to 0)
|
||||
*/
|
||||
void Adafruit_Si4713::setTXpower(uint8_t pwr, uint8_t antcap) {
|
||||
_i2ccommand[0] = SI4710_CMD_TX_TUNE_POWER;
|
||||
_i2ccommand[1] = 0;
|
||||
_i2ccommand[2] = 0;
|
||||
_i2ccommand[3] = pwr;
|
||||
_i2ccommand[4] = antcap;
|
||||
sendCommand(5);
|
||||
}
|
||||
/*!
|
||||
* @brief Queries the TX status and input audio signal metrics.
|
||||
*/
|
||||
void Adafruit_Si4713::readASQ() {
|
||||
_i2ccommand[0] = SI4710_CMD_TX_ASQ_STATUS;
|
||||
_i2ccommand[1] = 0x1;
|
||||
sendCommand(2);
|
||||
|
||||
uint8_t resp[5];
|
||||
i2c_dev->read(resp, 5);
|
||||
currASQ = resp[1];
|
||||
currInLevel = resp[4];
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Queries the status of a previously sent TX Tune Freq, TX Tune
|
||||
* Power, or TX Tune Measure using SI4710_CMD_TX_TUNE_STATUS command.
|
||||
*/
|
||||
void Adafruit_Si4713::readTuneStatus() {
|
||||
_i2ccommand[0] = SI4710_CMD_TX_TUNE_STATUS;
|
||||
_i2ccommand[1] = 0x1; // INTACK
|
||||
sendCommand(2);
|
||||
|
||||
uint8_t resp[8];
|
||||
i2c_dev->read(resp, 8);
|
||||
currFreq = (uint16_t(resp[2]) << 8) | resp[3];
|
||||
currdBuV = resp[5];
|
||||
currAntCap = resp[6];
|
||||
currNoiseLevel = resp[7];
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Measure the received noise level at the specified frequency using
|
||||
* SI4710_CMD_TX_TUNE_MEASURE command.
|
||||
* @param freq
|
||||
* frequency
|
||||
*/
|
||||
void Adafruit_Si4713::readTuneMeasure(uint16_t freq) {
|
||||
// check freq is multiple of 50khz
|
||||
if (freq % 5 != 0) {
|
||||
freq -= (freq % 5);
|
||||
}
|
||||
// Serial.print("Measuring "); Serial.println(freq);
|
||||
_i2ccommand[0] = SI4710_CMD_TX_TUNE_MEASURE;
|
||||
_i2ccommand[1] = 0;
|
||||
_i2ccommand[2] = freq >> 8;
|
||||
_i2ccommand[3] = freq;
|
||||
_i2ccommand[4] = 0;
|
||||
|
||||
sendCommand(5);
|
||||
while (getStatus() != 0x81)
|
||||
delay(10);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Begin RDS
|
||||
* Sets properties as follows:
|
||||
* SI4713_PROP_TX_AUDIO_DEVIATION: 66.25KHz,
|
||||
* SI4713_PROP_TX_RDS_DEVIATION: 2KHz,
|
||||
* SI4713_PROP_TX_RDS_INTERRUPT_SOURCE: 1,
|
||||
* SI4713_PROP_TX_RDS_PS_MIX: 50% mix (default value),
|
||||
* SI4713_PROP_TX_RDS_PS_MISC: 0x1008,
|
||||
* SI4713_PROP_TX_RDS_PS_REPEAT_COUNT: 3,
|
||||
* SI4713_PROP_TX_RDS_MESSAGE_COUNT: 1,
|
||||
* SI4713_PROP_TX_RDS_PS_AF: 0xE0E0,
|
||||
* SI4713_PROP_TX_RDS_FIFO_SIZE: 0,
|
||||
* SI4713_PROP_TX_COMPONENT_ENABLE: 7
|
||||
* @param programID
|
||||
* sets SI4713_PROP_TX_RDS_PI to parameter value
|
||||
*/
|
||||
void Adafruit_Si4713::beginRDS(uint16_t programID) {
|
||||
setProperty(SI4713_PROP_TX_AUDIO_DEVIATION,
|
||||
6625); // 66.25KHz (default is 68.25)
|
||||
setProperty(SI4713_PROP_TX_RDS_DEVIATION, 200); // 2KHz (default)
|
||||
setProperty(SI4713_PROP_TX_RDS_INTERRUPT_SOURCE, 0x0001); // RDS IRQ
|
||||
setProperty(SI4713_PROP_TX_RDS_PI, programID); // program identifier
|
||||
setProperty(SI4713_PROP_TX_RDS_PS_MIX, 0x03); // 50% mix (default)
|
||||
setProperty(SI4713_PROP_TX_RDS_PS_MISC, 0x1008); // RDSD0 & RDSMS (default)
|
||||
setProperty(SI4713_PROP_TX_RDS_PS_REPEAT_COUNT, 3); // 3 repeats (default)
|
||||
setProperty(SI4713_PROP_TX_RDS_MESSAGE_COUNT, 1); // 1 message (default)
|
||||
setProperty(SI4713_PROP_TX_RDS_PS_AF, 0xE0E0); // no AF (default)
|
||||
setProperty(SI4713_PROP_TX_RDS_FIFO_SIZE, 0); // no FIFO (default)
|
||||
setProperty(SI4713_PROP_TX_COMPONENT_ENABLE,
|
||||
0x0007); // enable RDS, stereo, tone
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Set up the RDS station string
|
||||
* @param *s
|
||||
* string to load
|
||||
*/
|
||||
void Adafruit_Si4713::setRDSstation(char *s) {
|
||||
uint8_t len = strlen(s);
|
||||
uint8_t slots = (len + 3) / 4;
|
||||
|
||||
for (uint8_t i = 0; i < slots; i++) {
|
||||
memset(_i2ccommand, ' ', 6); // clear it with ' '
|
||||
memcpy(_i2ccommand + 2, s, min(4, (int)strlen(s)));
|
||||
s += 4;
|
||||
_i2ccommand[6] = 0;
|
||||
#ifdef SI4713_CMD_DEBUG
|
||||
Serial.print("Set slot #");
|
||||
Serial.print(i);
|
||||
char *slot = (char *)(_i2ccommand + 2);
|
||||
Serial.print(" to '");
|
||||
Serial.print(slot);
|
||||
Serial.println("'");
|
||||
#endif
|
||||
_i2ccommand[0] = SI4710_CMD_TX_RDS_PS;
|
||||
_i2ccommand[1] = i; // slot #
|
||||
sendCommand(6);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Queries the status of the RDS Group Buffer and loads new data into
|
||||
* buffer.
|
||||
* @param *s
|
||||
* string to load
|
||||
*/
|
||||
void Adafruit_Si4713::setRDSbuffer(char *s) {
|
||||
uint8_t len = strlen(s);
|
||||
uint8_t slots = (len + 3) / 4;
|
||||
char slot[5];
|
||||
|
||||
for (uint8_t i = 0; i < slots; i++) {
|
||||
memset(_i2ccommand, ' ', 8); // clear it with ' '
|
||||
memcpy(_i2ccommand + 4, s, min(4, (int)strlen(s)));
|
||||
s += 4;
|
||||
_i2ccommand[8] = 0;
|
||||
#ifdef SI4713_CMD_DEBUG
|
||||
Serial.print("Set buff #");
|
||||
Serial.print(i);
|
||||
char *slot = (char *)(_i2ccommand + 4);
|
||||
Serial.print(" to '");
|
||||
Serial.print(slot);
|
||||
Serial.println("'");
|
||||
#endif
|
||||
_i2ccommand[0] = SI4710_CMD_TX_RDS_BUFF;
|
||||
if (i == 0)
|
||||
_i2ccommand[1] = 0x06;
|
||||
else
|
||||
_i2ccommand[1] = 0x04;
|
||||
|
||||
_i2ccommand[2] = 0x20;
|
||||
_i2ccommand[3] = i;
|
||||
sendCommand(8);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Read interrupt status bits.
|
||||
* @return status bits
|
||||
*/
|
||||
uint8_t Adafruit_Si4713::getStatus() {
|
||||
uint8_t resp[1] = {SI4710_CMD_GET_INT_STATUS};
|
||||
i2c_dev->write_then_read(resp, 1, resp, 1);
|
||||
return resp[0];
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Sends power up command to the breakout, than CTS and GPO2 output
|
||||
* is disabled and than enable xtal oscilator. Also It sets properties:
|
||||
* SI4713_PROP_REFCLK_FREQ: 32.768
|
||||
* SI4713_PROP_TX_PREEMPHASIS: 74uS pre-emph (USA standard)
|
||||
* SI4713_PROP_TX_ACOMP_GAIN: max gain
|
||||
* SI4713_PROP_TX_ACOMP_ENABLE: turned on limiter and AGC
|
||||
*/
|
||||
void Adafruit_Si4713::powerUp() {
|
||||
_i2ccommand[0] = SI4710_CMD_POWER_UP;
|
||||
_i2ccommand[1] = 0x12;
|
||||
// CTS interrupt disabled
|
||||
// GPO2 output disabled
|
||||
// Boot normally
|
||||
// xtal oscillator ENabled
|
||||
// FM transmit
|
||||
_i2ccommand[2] = 0x50; // analog input mode
|
||||
sendCommand(3);
|
||||
|
||||
// configuration! see page 254
|
||||
setProperty(SI4713_PROP_REFCLK_FREQ, 32768); // crystal is 32.768
|
||||
setProperty(SI4713_PROP_TX_PREEMPHASIS, 0); // 74uS pre-emph (USA std)
|
||||
setProperty(SI4713_PROP_TX_ACOMP_GAIN, 10); // max gain?
|
||||
// setProperty(SI4713_PROP_TX_ACOMP_ENABLE, 0x02); // turn on limiter, but no
|
||||
// dynamic ranging
|
||||
setProperty(SI4713_PROP_TX_ACOMP_ENABLE, 0x0); // turn on limiter and AGC
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Get the hardware revision code from the device using
|
||||
* SI4710_CMD_GET_REV
|
||||
* @return revision number
|
||||
*/
|
||||
uint8_t Adafruit_Si4713::getRev() {
|
||||
_i2ccommand[0] = SI4710_CMD_GET_REV;
|
||||
_i2ccommand[1] = 0;
|
||||
sendCommand(2);
|
||||
|
||||
uint8_t pn, fw, patch, cmp, chiprev, resp[9];
|
||||
i2c_dev->read(resp, 9);
|
||||
pn = resp[1];
|
||||
fw = (uint16_t(resp[2]) << 8) | resp[3];
|
||||
patch = (uint16_t(resp[4]) << 8) | resp[5];
|
||||
cmp = (uint16_t(resp[6]) << 8) | resp[7];
|
||||
chiprev = resp[8];
|
||||
|
||||
#ifdef SI4713_CMD_DEBUG
|
||||
Serial.print("Part # Si47");
|
||||
Serial.print(pn);
|
||||
Serial.print("-");
|
||||
Serial.println(fw, HEX);
|
||||
Serial.print("Firmware 0x");
|
||||
Serial.println(fw, HEX);
|
||||
Serial.print("Patch 0x");
|
||||
Serial.println(patch, HEX);
|
||||
Serial.print("Chip rev ");
|
||||
Serial.write(chiprev);
|
||||
Serial.println();
|
||||
#endif
|
||||
|
||||
return pn;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Configures GP1 / GP2 as output or Hi-Z.
|
||||
* @param x
|
||||
* bit value
|
||||
*/
|
||||
void Adafruit_Si4713::setGPIOctrl(uint8_t x) {
|
||||
#ifdef SI4713_CMD_DEBUG
|
||||
Serial.println("GPIO direction");
|
||||
#endif
|
||||
_i2ccommand[0] = SI4710_CMD_GPO_CTL;
|
||||
_i2ccommand[1] = x;
|
||||
sendCommand(2);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Sets GP1 / GP2 output level (low or high).
|
||||
* @param x
|
||||
* bit value
|
||||
*/
|
||||
void Adafruit_Si4713::setGPIO(uint8_t x) {
|
||||
#ifdef SI4713_CMD_DEBUG
|
||||
Serial.println("GPIO set");
|
||||
#endif
|
||||
_i2ccommand[0] = SI4710_CMD_GPO_SET;
|
||||
_i2ccommand[1] = x;
|
||||
sendCommand(2);
|
||||
}
|
190
src/Adafruit_Si4713.h
Normal file
190
src/Adafruit_Si4713.h
Normal file
@ -0,0 +1,190 @@
|
||||
/*!
|
||||
* @file Adafruit_Si4713.h
|
||||
*
|
||||
* This is a library for the Si4713 breakout
|
||||
*
|
||||
* Designed specifically to work with the Adafruit Si4713 breakout
|
||||
*
|
||||
* Pick one up today in the adafruit shop!
|
||||
* ------> https://www.adafruit.com/product/1958
|
||||
*
|
||||
* These transmitters use I2C to communicate, plus reset pin,
|
||||
* 3 pins are required to interface
|
||||
*
|
||||
* Adafruit invests time and resources providing this open source code,
|
||||
* please support Adafruit andopen-source hardware by purchasing products
|
||||
* from Adafruit!
|
||||
*
|
||||
* Limor Fried/Ladyada (Adafruit Industries).
|
||||
*
|
||||
* BSD license, all text above must be included in any redistribution
|
||||
*/
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <Adafruit_BusIO-master/Adafruit_I2CDevice.h>
|
||||
|
||||
//#define SI471X_CMD_DEBUG
|
||||
#define SI4710_ADDR0 0x11 ///< if SEN is low
|
||||
#define SI4710_ADDR1 0x63 ///< if SEN is high, default!
|
||||
#define SI4710_STATUS_CTS 0x80 ///< read status
|
||||
|
||||
/* COMMANDS */
|
||||
#define SI4710_CMD_POWER_UP \
|
||||
0x01 ///< Power up device and mode selection. Modes include FM transmit and
|
||||
///< analog/digital audio interface configuration.
|
||||
#define SI4710_CMD_GET_REV 0x10 ///< Returns revision information on the device.
|
||||
#define SI4710_CMD_POWER_DOWN 0x11 ///< Power down device
|
||||
#define SI4710_CMD_SET_PROPERTY 0x12 ///< Sets the value of a property.
|
||||
#define SI4710_CMD_GET_PROPERTY 0x13 ///< Retrieves a property’s value.
|
||||
#define SI4710_CMD_GET_INT_STATUS 0x14 ///< Read interrupt status bits.
|
||||
#define SI4710_CMD_PATCH_ARGS \
|
||||
0x15 ///< Reserved command used for patch file downloads.
|
||||
#define SI4710_CMD_PATCH_DATA \
|
||||
0x16 ///< Reserved command used for patch file downloads.
|
||||
#define SI4710_CMD_TX_TUNE_FREQ 0x30 ///< Tunes to given transmit frequency.
|
||||
#define SI4710_CMD_TX_TUNE_POWER \
|
||||
0x31 ///< Sets the output power level and tunes the antenna capacitor
|
||||
#define SI4710_CMD_TX_TUNE_MEASURE \
|
||||
0x32 ///< Measure the received noise level at the specified frequency.
|
||||
#define SI4710_CMD_TX_TUNE_STATUS \
|
||||
0x33 ///< Queries the status of a previously sent TX Tune Freq, TX Tune Power,
|
||||
///< or TX Tune Measure command.
|
||||
#define SI4710_CMD_TX_ASQ_STATUS \
|
||||
0x34 ///< Queries the TX status and input audio signal metrics.
|
||||
#define SI4710_CMD_TX_RDS_BUFF \
|
||||
0x35 ///< Queries the status of the RDS Group Buffer and loads new data into
|
||||
///< buffer.
|
||||
#define SI4710_CMD_TX_RDS_PS 0x36 ///< Set up default PS strings
|
||||
#define SI4710_CMD_GPO_CTL 0x80 ///< Configures GPO3 as output or Hi-Z.
|
||||
#define SI4710_CMD_GPO_SET 0x81 ///< Sets GPO3 output level (low or high).
|
||||
|
||||
/* Parameters */
|
||||
#define SI4713_PROP_GPO_IEN 0x0001 ///< Enables interrupt sources.
|
||||
#define SI4713_PROP_DIGITAL_INPUT_FORMAT \
|
||||
0x0101 ///< Configures the digital input format.
|
||||
#define SI4713_PROP_DIGITAL_INPUT_SAMPLE_RATE \
|
||||
0x0103 ///< Configures the digital input sample rate in 10 Hz steps. Default
|
||||
///< is 0.
|
||||
#define SI4713_PROP_REFCLK_FREQ \
|
||||
0x0201 ///< Sets frequency of the reference clock in Hz. The range is 31130 to
|
||||
///< 34406 Hz, or 0 to disable the AFC. Default is 32768 Hz.
|
||||
#define SI4713_PROP_REFCLK_PRESCALE \
|
||||
0x0202 ///< Sets the prescaler value for the reference clock.
|
||||
#define SI4713_PROP_TX_COMPONENT_ENABLE \
|
||||
0x2100 ///< Enable transmit multiplex signal components. Default has pilot and
|
||||
///< L-R enabled.
|
||||
#define SI4713_PROP_TX_AUDIO_DEVIATION \
|
||||
0x2101 ///< Configures audio frequency deviation level. Units are in 10 Hz
|
||||
///< increments. Default is 6285 (68.25 kHz).
|
||||
#define SI4713_PROP_TX_PILOT_DEVIATION \
|
||||
0x2102 ///< Configures pilot tone frequency deviation level. Units are in 10
|
||||
///< Hz increments. Default is 675 (6.75 kHz)
|
||||
#define SI4713_PROP_TX_RDS_DEVIATION \
|
||||
0x2103 ///< Configures the RDS/RBDS frequency deviation level. Units are in 10
|
||||
///< Hz increments. Default is 2 kHz.
|
||||
#define SI4713_PROP_TX_LINE_LEVEL_INPUT_LEVEL \
|
||||
0x2104 ///< Configures maximum analog line input level to the LIN/RIN pins to
|
||||
///< reach the maximum deviation level pro- grammed into the audio
|
||||
///< deviation property TX Audio Deviation. Default is 636 mVPK.
|
||||
#define SI4713_PROP_TX_LINE_INPUT_MUTE \
|
||||
0x2105 ///< Sets line input mute. L and R inputs may be indepen- dently muted.
|
||||
///< Default is not muted.
|
||||
#define SI4713_PROP_TX_PREEMPHASIS \
|
||||
0x2106 ///< Configures pre-emphasis time constant. Default is 0 (75 μS).
|
||||
#define SI4713_PROP_TX_PILOT_FREQUENCY \
|
||||
0x2107 ///< Configures the frequency of the stereo pilot. Default is 19000 Hz.
|
||||
#define SI4713_PROP_TX_ACOMP_ENABLE \
|
||||
0x2200 ///< Enables audio dynamic range control. Default is 0 (disabled).
|
||||
#define SI4713_PROP_TX_ACOMP_THRESHOLD \
|
||||
0x2201 ///< Sets the threshold level for audio dynamic range control. Default
|
||||
///< is –40 dB.
|
||||
#define SI4713_PROP_TX_ATTACK_TIME \
|
||||
0x2202 ///< Sets the attack time for audio dynamic range control. Default is 0
|
||||
///< (0.5 ms).
|
||||
#define SI4713_PROP_TX_RELEASE_TIME \
|
||||
0x2203 ///< Sets the release time for audio dynamic range control. Default is
|
||||
///< 4 (1000 ms).
|
||||
#define SI4713_PROP_TX_ACOMP_GAIN \
|
||||
0x2204 ///< Sets the gain for audio dynamic range control. Default is 15 dB.
|
||||
#define SI4713_PROP_TX_LIMITER_RELEASE_TIME \
|
||||
0x2205 ///< Sets the limiter release time. Default is 102 (5.01 ms)
|
||||
#define SI4713_PROP_TX_ASQ_INTERRUPT_SOURCE \
|
||||
0x2300 ///< Configures measurements related to signal quality met- rics.
|
||||
///< Default is none selected.
|
||||
#define SI4713_PROP_TX_ASQ_LEVEL_LOW \
|
||||
0x2301 ///< Configures low audio input level detection threshold. This
|
||||
///< threshold can be used to detect silence on the incoming audio.
|
||||
#define SI4713_PROP_TX_ASQ_DURATION_LOW \
|
||||
0x2302 ///< Configures the duration which the input audio level must be below
|
||||
///< the low threshold in order to detect a low audio condition.
|
||||
#define SI4713_PROP_TX_AQS_LEVEL_HIGH \
|
||||
0x2303 ///< Configures high audio input level detection threshold. This
|
||||
///< threshold can be used to detect activity on the incoming audio.
|
||||
#define SI4713_PROP_TX_AQS_DURATION_HIGH \
|
||||
0x2304 ///< Configures the duration which the input audio level must be above
|
||||
///< the high threshold in order to detect a high audio condition.
|
||||
#define SI4713_PROP_TX_RDS_INTERRUPT_SOURCE \
|
||||
0x2C00 ///< Configure RDS interrupt sources. Default is none selected.
|
||||
#define SI4713_PROP_TX_RDS_PI 0x2C01 ///< Sets transmit RDS program identifier.
|
||||
#define SI4713_PROP_TX_RDS_PS_MIX \
|
||||
0x2C02 ///< Configures mix of RDS PS Group with RDS Group Buffer.
|
||||
#define SI4713_PROP_TX_RDS_PS_MISC \
|
||||
0x2C03 ///< Miscellaneous bits to transmit along with RDS_PS Groups.
|
||||
#define SI4713_PROP_TX_RDS_PS_REPEAT_COUNT \
|
||||
0x2C04 ///< Number of times to repeat transmission of a PS message before
|
||||
///< transmitting the next PS mes- sage.
|
||||
#define SI4713_PROP_TX_RDS_MESSAGE_COUNT \
|
||||
0x2C05 ///< Number of PS messages in use.
|
||||
#define SI4713_PROP_TX_RDS_PS_AF \
|
||||
0x2C06 ///< RDS Program Service Alternate Fre- quency. This provides the
|
||||
///< ability to inform the receiver of a single alternate frequency
|
||||
///< using AF Method A coding and is transmitted along with the RDS_PS
|
||||
///< Groups.
|
||||
#define SI4713_PROP_TX_RDS_FIFO_SIZE \
|
||||
0x2C07 ///< Number of blocks reserved for the FIFO. Note that the value
|
||||
///< written must be one larger than the desired FIFO size.
|
||||
|
||||
/*!
|
||||
* @brief Class that stores state and functions for interacting with
|
||||
* Si4713 breakout
|
||||
*/
|
||||
class Adafruit_Si4713 {
|
||||
public:
|
||||
Adafruit_Si4713(int8_t rstpin = -1);
|
||||
bool begin(uint8_t addr = SI4710_ADDR1, TwoWire *theWire = &Wire);
|
||||
void reset();
|
||||
|
||||
void powerUp();
|
||||
// void configure();
|
||||
uint8_t getRev();
|
||||
|
||||
void tuneFM(uint16_t freqKHz);
|
||||
uint8_t getStatus(void);
|
||||
void readTuneStatus(void);
|
||||
void readTuneMeasure(uint16_t freq);
|
||||
void setTXpower(uint8_t pwr, uint8_t antcap = 0);
|
||||
void readASQ(void);
|
||||
void setProperty(uint16_t p, uint16_t v);
|
||||
|
||||
// RDS stuff
|
||||
void beginRDS(uint16_t programID = 0xADAF);
|
||||
void setRDSstation(char *s);
|
||||
void setRDSbuffer(char *s);
|
||||
|
||||
uint16_t currFreq; ///< current frequency
|
||||
uint8_t currdBuV, ///< current BuV
|
||||
currAntCap, ///< current antenna capacitor
|
||||
currNoiseLevel, ///< current noise level
|
||||
currASQ; ///< current ASQ
|
||||
int8_t currInLevel; ///< current IN level
|
||||
|
||||
void setGPIO(uint8_t x);
|
||||
void setGPIOctrl(uint8_t x);
|
||||
|
||||
private:
|
||||
void sendCommand(uint8_t len);
|
||||
|
||||
int8_t _rst;
|
||||
uint8_t _i2ccommand[10]; // holds the command buffer
|
||||
Adafruit_I2CDevice *i2c_dev = NULL;
|
||||
};
|
26
src/config.h
Normal file
26
src/config.h
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// Created by vlad on 18.02.2021.
|
||||
//
|
||||
|
||||
#ifndef GBO_WORK0001_ESP32_CONFIG_H
|
||||
#define GBO_WORK0001_ESP32_CONFIG_H
|
||||
|
||||
#include "Adafruit_Si4713.h"
|
||||
|
||||
#define SERIAL_DEBUG 1
|
||||
#define IF_DEBUG if (SERIAL_DEBUG)
|
||||
|
||||
#define WIFI_SSID "XOXJLbl nugapacbl"
|
||||
#define WIFI_PSK "minetZaMinet"
|
||||
|
||||
|
||||
#define FM_RST_PIN 13
|
||||
|
||||
//#define OUT_CHANNEL_0 13
|
||||
//#define OUT_CHANNEL_1 12
|
||||
//#define OUT_CHANNEL_2 14
|
||||
//#define OUT_CHANNEL_3 27
|
||||
|
||||
extern Adafruit_Si4713* radio;
|
||||
|
||||
#endif //GBO_WORK0001_ESP32_CONFIG_H
|
17
src/http/ConnectionContext.cpp
Normal file
17
src/http/ConnectionContext.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
#include "ConnectionContext.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
ConnectionContext::ConnectionContext() {
|
||||
|
||||
}
|
||||
|
||||
ConnectionContext::~ConnectionContext() {
|
||||
|
||||
}
|
||||
|
||||
void ConnectionContext::setWebsocketHandler(WebsocketHandler *wsHandler) {
|
||||
_wsHandler = wsHandler;
|
||||
}
|
||||
|
||||
} /* namespace httpsserver */
|
41
src/http/ConnectionContext.hpp
Normal file
41
src/http/ConnectionContext.hpp
Normal file
@ -0,0 +1,41 @@
|
||||
#ifndef SRC_CONNECTIONCONTEXT_HPP_
|
||||
#define SRC_CONNECTIONCONTEXT_HPP_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <IPAddress.h>
|
||||
|
||||
// Required for SSL
|
||||
#include "openssl/ssl.h"
|
||||
#undef read
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
class WebsocketHandler;
|
||||
|
||||
/**
|
||||
* \brief Internal class to handle the state of a connection
|
||||
*/
|
||||
class ConnectionContext {
|
||||
public:
|
||||
ConnectionContext();
|
||||
virtual ~ConnectionContext();
|
||||
|
||||
virtual void signalRequestError() = 0;
|
||||
virtual void signalClientClose() = 0;
|
||||
virtual size_t getCacheSize() = 0;
|
||||
|
||||
virtual size_t readBuffer(byte* buffer, size_t length) = 0;
|
||||
virtual size_t pendingBufferSize() = 0;
|
||||
|
||||
virtual size_t writeBuffer(byte* buffer, size_t length) = 0;
|
||||
|
||||
virtual bool isSecure() = 0;
|
||||
virtual void setWebsocketHandler(WebsocketHandler *wsHandler);
|
||||
virtual IPAddress getClientIP() = 0;
|
||||
|
||||
WebsocketHandler * _wsHandler;
|
||||
};
|
||||
|
||||
} /* namespace httpsserver */
|
||||
|
||||
#endif /* SRC_CONNECTIONCONTEXT_HPP_ */
|
68
src/http/HTTPBodyParser.hpp
Normal file
68
src/http/HTTPBodyParser.hpp
Normal file
@ -0,0 +1,68 @@
|
||||
#ifndef SRC_HTTPBODYPARSER_HPP_
|
||||
#define SRC_HTTPBODYPARSER_HPP_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <string>
|
||||
#include "HTTPRequest.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
/**
|
||||
* Superclass for various body parser implementations that can be used to
|
||||
* interpret http-specific bodies (like x-www-form-urlencoded or multipart/form-data)
|
||||
*
|
||||
* To allow for arbitrary body length, the interface of the body parser provides access
|
||||
* to one underlying "field" at a time. A field may be a value of the urlencoded string
|
||||
* or a part of a multipart message.
|
||||
*
|
||||
* Using next() proceeds to the next field.
|
||||
*/
|
||||
class HTTPBodyParser {
|
||||
public:
|
||||
const size_t unknownLength = 0x7ffffffe;
|
||||
|
||||
HTTPBodyParser(HTTPRequest * req): _request(req) {};
|
||||
virtual ~HTTPBodyParser() {}
|
||||
|
||||
/**
|
||||
* Proceeds to the next field of the body
|
||||
*
|
||||
* If a field has not been read completely, the remaining content is discarded.
|
||||
*
|
||||
* Returns true iff proceeding to the next field succeeded (ie there was a next field)
|
||||
*/
|
||||
virtual bool nextField() = 0;
|
||||
|
||||
/** Returns the name of the current field */
|
||||
virtual std::string getFieldName() = 0;
|
||||
|
||||
/** Returns the filename of the current field or an empty string */
|
||||
virtual std::string getFieldFilename() = 0;
|
||||
|
||||
/**
|
||||
* Returns the mime type of the current field.
|
||||
*
|
||||
* Note: This value is set by the client. It can be altered maliciously. Do NOT rely on it
|
||||
* for anything that affects the security of your device or other clients connected to it!
|
||||
*
|
||||
* Not every BodyParser might provide this value, usually it's set to something like text/plain then
|
||||
*/
|
||||
virtual std::string getFieldMimeType() = 0;
|
||||
|
||||
/**
|
||||
* Reads a maximum of bufferSize bytes into buffer and returns the actual amount of bytes that have been read
|
||||
*/
|
||||
virtual size_t read(byte* buffer, size_t bufferSize) = 0;
|
||||
|
||||
/** Returns true when all field data has been read */
|
||||
virtual bool endOfField() = 0;
|
||||
|
||||
|
||||
protected:
|
||||
/** The underlying request */
|
||||
HTTPRequest * _request;
|
||||
};
|
||||
|
||||
} // namespace httpserver
|
||||
|
||||
#endif
|
691
src/http/HTTPConnection.cpp
Normal file
691
src/http/HTTPConnection.cpp
Normal file
@ -0,0 +1,691 @@
|
||||
#include "HTTPConnection.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
HTTPConnection::HTTPConnection(ResourceResolver * resResolver):
|
||||
_resResolver(resResolver) {
|
||||
_socket = -1;
|
||||
_addrLen = 0;
|
||||
|
||||
_bufferProcessed = 0;
|
||||
_bufferUnusedIdx = 0;
|
||||
|
||||
_connectionState = STATE_UNDEFINED;
|
||||
_clientState = CSTATE_UNDEFINED;
|
||||
_httpHeaders = NULL;
|
||||
_defaultHeaders = NULL;
|
||||
_isKeepAlive = false;
|
||||
_lastTransmissionTS = millis();
|
||||
_shutdownTS = 0;
|
||||
_wsHandler = nullptr;
|
||||
}
|
||||
|
||||
HTTPConnection::~HTTPConnection() {
|
||||
// Close the socket
|
||||
closeConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the connection from a server socket.
|
||||
*
|
||||
* The call WILL BLOCK if accept(serverSocketID) blocks. So use select() to check for that in advance.
|
||||
*/
|
||||
int HTTPConnection::initialize(int serverSocketID, HTTPHeaders *defaultHeaders) {
|
||||
if (_connectionState == STATE_UNDEFINED) {
|
||||
_defaultHeaders = defaultHeaders;
|
||||
_addrLen = sizeof(_sockAddr);
|
||||
_socket = accept(serverSocketID, (struct sockaddr * )&_sockAddr, &_addrLen);
|
||||
|
||||
// Build up SSL Connection context if the socket has been created successfully
|
||||
if (_socket >= 0) {
|
||||
HTTPS_LOGI("New connection. Socket FID=%d", _socket);
|
||||
_connectionState = STATE_INITIAL;
|
||||
_httpHeaders = new HTTPHeaders();
|
||||
refreshTimeout();
|
||||
return _socket;
|
||||
}
|
||||
|
||||
HTTPS_LOGE("Could not accept() new connection");
|
||||
|
||||
_addrLen = 0;
|
||||
_connectionState = STATE_ERROR;
|
||||
_clientState = CSTATE_ACTIVE;
|
||||
|
||||
// This will only be called if the connection could not be established and cleanup
|
||||
// variables etc.
|
||||
closeConnection();
|
||||
}
|
||||
// Error: The connection has already been established or could not be established
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client's IPv4
|
||||
*/
|
||||
IPAddress HTTPConnection::getClientIP() {
|
||||
if (_addrLen > 0 && _sockAddr.sa_family == AF_INET) {
|
||||
struct sockaddr_in *sockAddrIn = (struct sockaddr_in *)(&_sockAddr);
|
||||
return IPAddress(sockAddrIn->sin_addr.s_addr);
|
||||
}
|
||||
return IPAddress(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the connection is timed out.
|
||||
*
|
||||
* (Should be checkd in the loop and transition should go to CONNECTION_CLOSE if exceeded)
|
||||
*/
|
||||
bool HTTPConnection::isTimeoutExceeded() {
|
||||
return _lastTransmissionTS + HTTPS_CONNECTION_TIMEOUT < millis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the timeout to allow again the full HTTPS_CONNECTION_TIMEOUT milliseconds
|
||||
*/
|
||||
void HTTPConnection::refreshTimeout() {
|
||||
_lastTransmissionTS = millis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true, if the connection has been closed.
|
||||
*/
|
||||
bool HTTPConnection::isClosed() {
|
||||
return (_connectionState == STATE_ERROR || _connectionState == STATE_CLOSED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true, if the connection has been closed due to error
|
||||
*/
|
||||
bool HTTPConnection::isError() {
|
||||
return (_connectionState == STATE_ERROR);
|
||||
}
|
||||
|
||||
bool HTTPConnection::isSecure() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void HTTPConnection::closeConnection() {
|
||||
// TODO: Call an event handler here, maybe?
|
||||
|
||||
if (_connectionState != STATE_ERROR && _connectionState != STATE_CLOSED) {
|
||||
|
||||
// First call to closeConnection - set the timestamp to calculate the timeout later on
|
||||
if (_connectionState != STATE_CLOSING) {
|
||||
_shutdownTS = millis();
|
||||
}
|
||||
|
||||
// Set the connection state to closing. We stay in closing as long as SSL has not been shutdown
|
||||
// correctly
|
||||
_connectionState = STATE_CLOSING;
|
||||
}
|
||||
|
||||
// Tear down the socket
|
||||
if (_socket >= 0) {
|
||||
HTTPS_LOGI("Connection closed. Socket FID=%d", _socket);
|
||||
close(_socket);
|
||||
_socket = -1;
|
||||
_addrLen = 0;
|
||||
}
|
||||
|
||||
if (_connectionState != STATE_ERROR) {
|
||||
_connectionState = STATE_CLOSED;
|
||||
}
|
||||
|
||||
if (_httpHeaders != NULL) {
|
||||
HTTPS_LOGD("Free headers");
|
||||
delete _httpHeaders;
|
||||
_httpHeaders = NULL;
|
||||
}
|
||||
|
||||
if (_wsHandler != nullptr) {
|
||||
HTTPS_LOGD("Free WS Handler");
|
||||
delete _wsHandler;
|
||||
_wsHandler = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will try to fill up the buffer with data from
|
||||
*/
|
||||
int HTTPConnection::updateBuffer() {
|
||||
if (!isClosed()) {
|
||||
|
||||
// If there is buffer data that has been marked as processed.
|
||||
// Some example is shown here:
|
||||
//
|
||||
// Previous configuration:
|
||||
// GET / HTTP/1.1\\Host: test\\Foo: bar\\\\[some uninitialized memory]
|
||||
// ^ processed ^ unusedIdx
|
||||
//
|
||||
// New configuration after shifting:
|
||||
// Host: test\\Foo: bar\\\\[some uninitialized memory]
|
||||
// ^ processed ^ unusedIdx
|
||||
if (_bufferProcessed > 0) {
|
||||
for(int i = 0; i < HTTPS_CONNECTION_DATA_CHUNK_SIZE; i++) {
|
||||
int copyFrom = i + _bufferProcessed;
|
||||
if (copyFrom < _bufferUnusedIdx) {
|
||||
_receiveBuffer[i] = _receiveBuffer[copyFrom];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_bufferUnusedIdx -= _bufferProcessed;
|
||||
_bufferProcessed = 0;
|
||||
|
||||
}
|
||||
|
||||
if (_bufferUnusedIdx < HTTPS_CONNECTION_DATA_CHUNK_SIZE) {
|
||||
if (canReadData()) {
|
||||
|
||||
HTTPS_LOGD("Data on Socket FID=%d", _socket);
|
||||
|
||||
int readReturnCode;
|
||||
|
||||
// The return code of SSL_read means:
|
||||
// > 0 : Length of the data that has been read
|
||||
// < 0 : Error
|
||||
// = 0 : Connection closed
|
||||
readReturnCode = readBytesToBuffer(
|
||||
// Only after the part of the buffer that has not been processed yet
|
||||
(byte*)(_receiveBuffer + sizeof(char) * _bufferUnusedIdx),
|
||||
// Only append up to the end of the buffer
|
||||
HTTPS_CONNECTION_DATA_CHUNK_SIZE - _bufferUnusedIdx
|
||||
);
|
||||
|
||||
if (readReturnCode > 0) {
|
||||
_bufferUnusedIdx += readReturnCode;
|
||||
refreshTimeout();
|
||||
return readReturnCode;
|
||||
|
||||
} else if (readReturnCode == 0) {
|
||||
// The connection has been closed by the client
|
||||
_clientState = CSTATE_CLOSED;
|
||||
HTTPS_LOGI("Client closed connection, FID=%d", _socket);
|
||||
// TODO: If we are in state websocket, we might need to do something here
|
||||
return 0;
|
||||
} else {
|
||||
// An error occured
|
||||
_connectionState = STATE_ERROR;
|
||||
HTTPS_LOGE("An receive error occured, FID=%d", _socket);
|
||||
closeConnection();
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // data pending
|
||||
|
||||
} // buffer can read more
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool HTTPConnection::canReadData() {
|
||||
fd_set sockfds;
|
||||
FD_ZERO( &sockfds );
|
||||
FD_SET(_socket, &sockfds);
|
||||
|
||||
// We define an immediate timeout (return immediately, if there's no data)
|
||||
timeval timeout;
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
// Check for input
|
||||
// As by 2017-12-14, it seems that FD_SETSIZE is defined as 0x40, but socket IDs now
|
||||
// start at 0x1000, so we need to use _socket+1 here
|
||||
select(_socket + 1, &sockfds, NULL, NULL, &timeout);
|
||||
|
||||
return FD_ISSET(_socket, &sockfds);
|
||||
}
|
||||
|
||||
size_t HTTPConnection::readBuffer(byte* buffer, size_t length) {
|
||||
updateBuffer();
|
||||
size_t bufferSize = _bufferUnusedIdx - _bufferProcessed;
|
||||
|
||||
if (length > bufferSize) {
|
||||
length = bufferSize;
|
||||
}
|
||||
|
||||
// Write until length is reached (either by param of by empty buffer
|
||||
for(int i = 0; i < length; i++) {
|
||||
buffer[i] = _receiveBuffer[_bufferProcessed++];
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
size_t HTTPConnection::pendingBufferSize() {
|
||||
updateBuffer();
|
||||
|
||||
return _bufferUnusedIdx - _bufferProcessed + pendingByteCount();
|
||||
}
|
||||
|
||||
size_t HTTPConnection::pendingByteCount() {
|
||||
return 0; // FIXME: Add the value of the equivalent function of SSL_pending() here
|
||||
}
|
||||
|
||||
size_t HTTPConnection::writeBuffer(byte* buffer, size_t length) {
|
||||
return send(_socket, buffer, length, 0);
|
||||
}
|
||||
|
||||
size_t HTTPConnection::readBytesToBuffer(byte* buffer, size_t length) {
|
||||
return recv(_socket, buffer, length, MSG_WAITALL | MSG_DONTWAIT);
|
||||
}
|
||||
|
||||
void HTTPConnection::raiseError(uint16_t code, std::string reason) {
|
||||
_connectionState = STATE_ERROR;
|
||||
std::string sCode = intToString(code);
|
||||
|
||||
char headers[] = "\r\nConnection: close\r\nContent-Type: text/plain;charset=utf8\r\n\r\n";
|
||||
writeBuffer((byte*)"HTTP/1.1 ", 9);
|
||||
writeBuffer((byte*)sCode.c_str(), sCode.length());
|
||||
writeBuffer((byte*)" ", 1);
|
||||
writeBuffer((byte*)(reason.c_str()), reason.length());
|
||||
writeBuffer((byte*)headers, strlen(headers));
|
||||
writeBuffer((byte*)sCode.c_str(), sCode.length());
|
||||
writeBuffer((byte*)" ", 1);
|
||||
writeBuffer((byte*)(reason.c_str()), reason.length());
|
||||
closeConnection();
|
||||
}
|
||||
|
||||
void HTTPConnection::readLine(int lengthLimit) {
|
||||
while(_bufferProcessed < _bufferUnusedIdx) {
|
||||
char newChar = _receiveBuffer[_bufferProcessed];
|
||||
|
||||
if ( newChar == '\r') {
|
||||
// Look ahead for \n (if not possible, wait for next round
|
||||
if (_bufferProcessed+1 < _bufferUnusedIdx) {
|
||||
if (_receiveBuffer[_bufferProcessed+1] == '\n') {
|
||||
_bufferProcessed += 2;
|
||||
_parserLine.parsingFinished = true;
|
||||
return;
|
||||
} else {
|
||||
// Line has not been terminated by \r\n
|
||||
HTTPS_LOGW("Line without \\r\\n (got only \\r). FID=%d", _socket);
|
||||
raiseError(400, "Bad Request");
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_parserLine.text += newChar;
|
||||
_bufferProcessed += 1;
|
||||
}
|
||||
|
||||
// Check that the max request string size is not exceeded
|
||||
if (_parserLine.text.length() > lengthLimit) {
|
||||
HTTPS_LOGW("Header length exceeded. FID=%d", _socket);
|
||||
raiseError(431, "Request Header Fields Too Large");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the request to signal that the client has closed the connection
|
||||
*/
|
||||
void HTTPConnection::signalClientClose() {
|
||||
_clientState = CSTATE_CLOSED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the request to signal that an error has occured
|
||||
*/
|
||||
void HTTPConnection::signalRequestError() {
|
||||
// TODO: Check that no response has been transmitted yet
|
||||
raiseError(400, "Bad Request");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cache size that should be cached (in the response) to enable keep-alive requests.
|
||||
*
|
||||
* 0 = no keep alive.
|
||||
*/
|
||||
size_t HTTPConnection::getCacheSize() {
|
||||
return (_isKeepAlive ? HTTPS_KEEPALIVE_CACHESIZE : 0);
|
||||
}
|
||||
|
||||
void HTTPConnection::loop() {
|
||||
// First, update the buffer
|
||||
// newByteCount will contain the number of new bytes that have to be processed
|
||||
updateBuffer();
|
||||
|
||||
if (_clientState == CSTATE_CLOSED) {
|
||||
HTTPS_LOGI("Client closed (FID=%d, cstate=%d)", _socket, _clientState);
|
||||
}
|
||||
|
||||
if (_clientState == CSTATE_CLOSED && _bufferProcessed == _bufferUnusedIdx && _connectionState < STATE_HEADERS_FINISHED) {
|
||||
closeConnection();
|
||||
}
|
||||
|
||||
if (!isClosed() && isTimeoutExceeded()) {
|
||||
HTTPS_LOGI("Connection timeout. FID=%d", _socket);
|
||||
closeConnection();
|
||||
}
|
||||
|
||||
if (!isError()) {
|
||||
// State machine (Reading request, reading headers, ...)
|
||||
switch(_connectionState) {
|
||||
case STATE_INITIAL: // Read request line
|
||||
readLine(HTTPS_REQUEST_MAX_REQUEST_LENGTH);
|
||||
if (_parserLine.parsingFinished && !isClosed()) {
|
||||
// Find the method
|
||||
size_t spaceAfterMethodIdx = _parserLine.text.find(' ');
|
||||
if (spaceAfterMethodIdx == std::string::npos) {
|
||||
HTTPS_LOGW("Missing space after method");
|
||||
raiseError(400, "Bad Request");
|
||||
break;
|
||||
}
|
||||
_httpMethod = _parserLine.text.substr(0, spaceAfterMethodIdx);
|
||||
|
||||
// Find the resource string:
|
||||
size_t spaceAfterResourceIdx = _parserLine.text.find(' ', spaceAfterMethodIdx + 1);
|
||||
if (spaceAfterResourceIdx == std::string::npos) {
|
||||
HTTPS_LOGW("Missing space after resource");
|
||||
raiseError(400, "Bad Request");
|
||||
break;
|
||||
}
|
||||
_httpResource = _parserLine.text.substr(spaceAfterMethodIdx + 1, spaceAfterResourceIdx - _httpMethod.length() - 1);
|
||||
|
||||
_parserLine.parsingFinished = false;
|
||||
_parserLine.text = "";
|
||||
HTTPS_LOGI("Request: %s %s (FID=%d)", _httpMethod.c_str(), _httpResource.c_str(), _socket);
|
||||
_connectionState = STATE_REQUEST_FINISHED;
|
||||
}
|
||||
|
||||
break;
|
||||
case STATE_REQUEST_FINISHED: // Read headers
|
||||
|
||||
while (_bufferProcessed < _bufferUnusedIdx && !isClosed()) {
|
||||
readLine(HTTPS_REQUEST_MAX_HEADER_LENGTH);
|
||||
if (_parserLine.parsingFinished && _connectionState != STATE_ERROR) {
|
||||
|
||||
if (_parserLine.text.empty()) {
|
||||
HTTPS_LOGD("Headers finished, FID=%d", _socket);
|
||||
_connectionState = STATE_HEADERS_FINISHED;
|
||||
|
||||
// Break, so that the rest of the body does not get flushed through
|
||||
_parserLine.parsingFinished = false;
|
||||
_parserLine.text = "";
|
||||
break;
|
||||
} else {
|
||||
int idxColon = _parserLine.text.find(':');
|
||||
if ( (idxColon != std::string::npos) && (_parserLine.text[idxColon+1]==' ') ) {
|
||||
_httpHeaders->set(new HTTPHeader(
|
||||
_parserLine.text.substr(0, idxColon),
|
||||
_parserLine.text.substr(idxColon+2)
|
||||
));
|
||||
HTTPS_LOGD("Header: %s = %s (FID=%d)", _parserLine.text.substr(0, idxColon).c_str(), _parserLine.text.substr(idxColon+2).c_str(), _socket);
|
||||
} else {
|
||||
HTTPS_LOGW("Malformed request header: %s", _parserLine.text.c_str());
|
||||
raiseError(400, "Bad Request");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_parserLine.parsingFinished = false;
|
||||
_parserLine.text = "";
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case STATE_HEADERS_FINISHED: // Handle body
|
||||
{
|
||||
HTTPS_LOGD("Resolving resource...");
|
||||
ResolvedResource resolvedResource;
|
||||
|
||||
// Check which kind of node we need (Websocket or regular)
|
||||
bool websocketRequested = checkWebsocket();
|
||||
|
||||
_resResolver->resolveNode(_httpMethod, _httpResource, resolvedResource, websocketRequested ? WEBSOCKET : HANDLER_CALLBACK);
|
||||
|
||||
// Is there any match (may be the defaultNode, if it is configured)
|
||||
if (resolvedResource.didMatch()) {
|
||||
// Check for client's request to keep-alive if we have a handler function.
|
||||
if (resolvedResource.getMatchingNode()->_nodeType == HANDLER_CALLBACK) {
|
||||
// Did the client set connection:keep-alive?
|
||||
HTTPHeader * connectionHeader = _httpHeaders->get("Connection");
|
||||
std::string connectionHeaderValue = "";
|
||||
if (connectionHeader != NULL) {
|
||||
connectionHeaderValue += connectionHeader->_value;
|
||||
std::transform(
|
||||
connectionHeaderValue.begin(),
|
||||
connectionHeaderValue.end(),
|
||||
connectionHeaderValue.begin(),
|
||||
[](unsigned char c){ return ::tolower(c); }
|
||||
);
|
||||
}
|
||||
if (std::string("keep-alive").compare(connectionHeaderValue)==0) {
|
||||
HTTPS_LOGD("Keep-Alive activated. FID=%d", _socket);
|
||||
_isKeepAlive = true;
|
||||
} else {
|
||||
HTTPS_LOGD("Keep-Alive disabled. FID=%d", _socket);
|
||||
_isKeepAlive = false;
|
||||
}
|
||||
} else {
|
||||
_isKeepAlive = false;
|
||||
}
|
||||
|
||||
// Create request context
|
||||
HTTPRequest req = HTTPRequest(
|
||||
this,
|
||||
_httpHeaders,
|
||||
resolvedResource.getMatchingNode(),
|
||||
_httpMethod,
|
||||
resolvedResource.getParams(),
|
||||
_httpResource
|
||||
);
|
||||
HTTPResponse res = HTTPResponse(this);
|
||||
|
||||
// Add default headers to the response
|
||||
auto allDefaultHeaders = _defaultHeaders->getAll();
|
||||
for(std::vector<HTTPHeader*>::iterator header = allDefaultHeaders->begin(); header != allDefaultHeaders->end(); ++header) {
|
||||
res.setHeader((*header)->_name, (*header)->_value);
|
||||
}
|
||||
|
||||
// Find the request handler callback
|
||||
HTTPSCallbackFunction * resourceCallback;
|
||||
if (websocketRequested) {
|
||||
// For the websocket, we use the handshake callback defined below
|
||||
resourceCallback = &handleWebsocketHandshake;
|
||||
} else {
|
||||
// For resource nodes, we use the callback defined by the node itself
|
||||
resourceCallback = ((ResourceNode*)resolvedResource.getMatchingNode())->_callback;
|
||||
}
|
||||
|
||||
// Get the current middleware chain
|
||||
auto vecMw = _resResolver->getMiddleware();
|
||||
|
||||
// Anchor of the chain is the actual resource. The call to the handler is bound here
|
||||
std::function<void()> next = std::function<void()>(std::bind(resourceCallback, &req, &res));
|
||||
|
||||
// Go back in the middleware chain and glue everything together
|
||||
auto itMw = vecMw.rbegin();
|
||||
while(itMw != vecMw.rend()) {
|
||||
next = std::function<void()>(std::bind((*itMw), &req, &res, next));
|
||||
itMw++;
|
||||
}
|
||||
|
||||
// We insert the internal validation middleware at the start of the chain:
|
||||
next = std::function<void()>(std::bind(&validationMiddleware, &req, &res, next));
|
||||
|
||||
// Call the whole chain
|
||||
next();
|
||||
|
||||
// The callback-function should have read all of the request body.
|
||||
// However, if it does not, we need to clear the request body now,
|
||||
// because otherwise it would be parsed in the next request.
|
||||
if (!req.requestComplete()) {
|
||||
HTTPS_LOGW("Callback function did not parse full request body");
|
||||
req.discardRequestBody();
|
||||
}
|
||||
|
||||
// Finally, after the handshake is done, we create the WebsocketHandler and change the internal state.
|
||||
if(websocketRequested) {
|
||||
_wsHandler = ((WebsocketNode*)resolvedResource.getMatchingNode())->newHandler();
|
||||
_wsHandler->initialize(this); // make websocket with this connection
|
||||
_connectionState = STATE_WEBSOCKET;
|
||||
} else {
|
||||
// Handling the request is done
|
||||
HTTPS_LOGD("Handler function done, request complete");
|
||||
|
||||
// Now we need to check if we can use keep-alive to reuse the SSL connection
|
||||
// However, if the client did not set content-size or defined connection: close,
|
||||
// we have no chance to do so.
|
||||
// Also, the programmer may have explicitly set Connection: close for the response.
|
||||
std::string hConnection = res.getHeader("Connection");
|
||||
if (hConnection == "close") {
|
||||
_isKeepAlive = false;
|
||||
}
|
||||
if (!_isKeepAlive) {
|
||||
// No KeepAlive -> We are done. Transition to next state.
|
||||
if (!isClosed()) {
|
||||
res.finalize();
|
||||
_connectionState = STATE_BODY_FINISHED;
|
||||
}
|
||||
} else {
|
||||
if (res.isResponseBuffered()) {
|
||||
// If the response could be buffered:
|
||||
res.setHeader("Connection", "keep-alive");
|
||||
res.finalize();
|
||||
if (_clientState != CSTATE_CLOSED) {
|
||||
// Refresh the timeout for the new request
|
||||
refreshTimeout();
|
||||
// Reset headers for the new connection
|
||||
_httpHeaders->clearAll();
|
||||
// Go back to initial state
|
||||
_connectionState = STATE_INITIAL;
|
||||
}
|
||||
}
|
||||
// The response could not be buffered or the client has closed:
|
||||
if (!isClosed() && _connectionState!=STATE_INITIAL) {
|
||||
_connectionState = STATE_BODY_FINISHED;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No match (no default route configured, nothing does match)
|
||||
HTTPS_LOGW("Could not find a matching resource");
|
||||
raiseError(404, "Not Found");
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case STATE_BODY_FINISHED: // Request is complete
|
||||
closeConnection();
|
||||
break;
|
||||
case STATE_CLOSING: // As long as we are in closing state, we call closeConnection() again and wait for it to finish or timeout
|
||||
closeConnection();
|
||||
break;
|
||||
case STATE_WEBSOCKET: // Do handling of the websocket
|
||||
refreshTimeout(); // don't timeout websocket connection
|
||||
if(pendingBufferSize() > 0) {
|
||||
HTTPS_LOGD("Calling WS handler, FID=%d", _socket);
|
||||
_wsHandler->loop();
|
||||
}
|
||||
|
||||
// If the client closed the connection unexpectedly
|
||||
if (_clientState == CSTATE_CLOSED) {
|
||||
HTTPS_LOGI("WS lost client, calling onClose, FID=%d", _socket);
|
||||
_wsHandler->onClose();
|
||||
}
|
||||
|
||||
// If the handler has terminated the connection, clean up and close the socket too
|
||||
if (_wsHandler->closed() || _clientState == CSTATE_CLOSED) {
|
||||
HTTPS_LOGI("WS closed, freeing Handler, FID=%d", _socket);
|
||||
delete _wsHandler;
|
||||
_wsHandler = nullptr;
|
||||
_connectionState = STATE_CLOSING;
|
||||
}
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool HTTPConnection::checkWebsocket() {
|
||||
if(_httpMethod == "GET" &&
|
||||
!_httpHeaders->getValue("Host").empty() &&
|
||||
_httpHeaders->getValue("Upgrade") == "websocket" &&
|
||||
_httpHeaders->getValue("Connection").find("Upgrade") != std::string::npos &&
|
||||
!_httpHeaders->getValue("Sec-WebSocket-Key").empty() &&
|
||||
_httpHeaders->getValue("Sec-WebSocket-Version") == "13") {
|
||||
|
||||
HTTPS_LOGI("Upgrading to WS, FID=%d", _socket);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware function that handles the validation of parameters
|
||||
*/
|
||||
void validationMiddleware(HTTPRequest * req, HTTPResponse * res, std::function<void()> next) {
|
||||
bool valid = true;
|
||||
// Get the matched node
|
||||
HTTPNode * node = req->getResolvedNode();
|
||||
// Get the parameters
|
||||
ResourceParameters * params = req->getParams();
|
||||
|
||||
// Iterate over the validators and run them
|
||||
std::vector<HTTPValidator*> * validators = node->getValidators();
|
||||
for(std::vector<HTTPValidator*>::iterator validator = validators->begin(); valid && validator != validators->end(); ++validator) {
|
||||
std::string param;
|
||||
if (params->getPathParameter((*validator)->_idx, param)) {
|
||||
valid = ((*validator)->_validatorFunction)(param);
|
||||
} else {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
next();
|
||||
} else {
|
||||
res->setStatusCode(400);
|
||||
res->setStatusText("Bad Request");
|
||||
res->print("400 Bad Request");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler function for the websocket handshake. Will be used by HTTPConnection if a websocket is detected
|
||||
*/
|
||||
void handleWebsocketHandshake(HTTPRequest * req, HTTPResponse * res) {
|
||||
res->setStatusCode(101);
|
||||
res->setStatusText("Switching Protocols");
|
||||
res->setHeader("Upgrade", "websocket");
|
||||
res->setHeader("Connection", "Upgrade");
|
||||
res->setHeader("Sec-WebSocket-Accept", websocketKeyResponseHash(req->getHeader("Sec-WebSocket-Key")));
|
||||
res->print("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Function used to compute the value of the Sec-WebSocket-Accept during Websocket handshake
|
||||
*/
|
||||
std::string websocketKeyResponseHash(std::string const &key) {
|
||||
std::string newKey = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
uint8_t shaData[HTTPS_SHA1_LENGTH];
|
||||
esp_sha(SHA1, (uint8_t*)newKey.data(), newKey.length(), shaData);
|
||||
|
||||
// Get output size required for base64 representation
|
||||
size_t b64BufferSize = 0;
|
||||
mbedtls_base64_encode(nullptr, 0, &b64BufferSize, (const unsigned char*)shaData, HTTPS_SHA1_LENGTH);
|
||||
|
||||
// Do the real encoding
|
||||
unsigned char bufferOut[b64BufferSize];
|
||||
size_t bytesEncoded = 0;
|
||||
int res = mbedtls_base64_encode(
|
||||
bufferOut,
|
||||
b64BufferSize,
|
||||
&bytesEncoded,
|
||||
(const unsigned char*)shaData,
|
||||
HTTPS_SHA1_LENGTH
|
||||
);
|
||||
|
||||
// Check result and return the encoded string
|
||||
if (res != 0) {
|
||||
return std::string();
|
||||
}
|
||||
return std::string((char*)bufferOut, bytesEncoded);
|
||||
} // WebsocketKeyResponseHash
|
||||
|
||||
} /* namespace httpsserver */
|
173
src/http/HTTPConnection.hpp
Normal file
173
src/http/HTTPConnection.hpp
Normal file
@ -0,0 +1,173 @@
|
||||
#ifndef SRC_HTTPCONNECTION_HPP_
|
||||
#define SRC_HTTPCONNECTION_HPP_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <IPAddress.h>
|
||||
|
||||
#include <string>
|
||||
#include <mbedtls/base64.h>
|
||||
#include <esp32/sha.h>
|
||||
#include <functional>
|
||||
|
||||
// Required for sockets
|
||||
#include "lwip/netdb.h"
|
||||
#undef read
|
||||
#include "lwip/sockets.h"
|
||||
|
||||
#include "HTTPSServerConstants.hpp"
|
||||
#include "ConnectionContext.hpp"
|
||||
|
||||
#include "HTTPHeaders.hpp"
|
||||
#include "HTTPHeader.hpp"
|
||||
|
||||
#include "ResourceResolver.hpp"
|
||||
#include "ResolvedResource.hpp"
|
||||
|
||||
#include "ResourceNode.hpp"
|
||||
#include "HTTPRequest.hpp"
|
||||
#include "HTTPResponse.hpp"
|
||||
|
||||
#include "WebsocketHandler.hpp"
|
||||
#include "WebsocketNode.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
/**
|
||||
* \brief Represents a single open connection for the plain HTTPServer, without TLS
|
||||
*/
|
||||
class HTTPConnection : private ConnectionContext {
|
||||
public:
|
||||
HTTPConnection(ResourceResolver * resResolver);
|
||||
virtual ~HTTPConnection();
|
||||
|
||||
virtual int initialize(int serverSocketID, HTTPHeaders *defaultHeaders);
|
||||
virtual void closeConnection();
|
||||
virtual bool isSecure();
|
||||
virtual IPAddress getClientIP();
|
||||
|
||||
void loop();
|
||||
bool isClosed();
|
||||
bool isError();
|
||||
|
||||
protected:
|
||||
friend class HTTPRequest;
|
||||
friend class HTTPResponse;
|
||||
friend class WebsocketInputStreambuf;
|
||||
|
||||
virtual size_t writeBuffer(byte* buffer, size_t length);
|
||||
virtual size_t readBytesToBuffer(byte* buffer, size_t length);
|
||||
virtual bool canReadData();
|
||||
virtual size_t pendingByteCount();
|
||||
|
||||
// Timestamp of the last transmission action
|
||||
unsigned long _lastTransmissionTS;
|
||||
|
||||
// Timestamp of when the shutdown was started
|
||||
unsigned long _shutdownTS;
|
||||
|
||||
// Internal state machine of the connection:
|
||||
//
|
||||
// O --- > STATE_UNDEFINED -- initialize() --> STATE_INITIAL -- get / http/1.1 --> STATE_REQUEST_FINISHED --.
|
||||
// | | | |
|
||||
// | | | | Host: ...\r\n
|
||||
// STATE_ERROR <- on error-----------------------<---------------------------------------< | Foo: bar\r\n
|
||||
// ^ | | | \r\n
|
||||
// | shutdown .--> STATE_CLOSED | | | \r\n
|
||||
// | fails | | | |
|
||||
// | | close() | | |
|
||||
// STATE_CLOSING <---- STATE_WEBSOCKET <-. | | |
|
||||
// ^ | | | |
|
||||
// `---------- close() ---------- STATE_BODY_FINISHED <-- Body received or GET -- STATE_HEADERS_FINISHED <-´
|
||||
//
|
||||
enum {
|
||||
// The order is important, to be able to use state <= STATE_HEADERS_FINISHED etc.
|
||||
|
||||
// The connection has not been established yet
|
||||
STATE_UNDEFINED,
|
||||
// The connection has just been created
|
||||
STATE_INITIAL,
|
||||
// The request line has been parsed
|
||||
STATE_REQUEST_FINISHED,
|
||||
// The headers have been parsed
|
||||
STATE_HEADERS_FINISHED,
|
||||
// The body has been parsed/the complete request has been processed (GET has body of length 0)
|
||||
STATE_BODY_FINISHED,
|
||||
// The connection is in websocket mode
|
||||
STATE_WEBSOCKET,
|
||||
// The connection is about to close (and waiting for the client to send close notify)
|
||||
STATE_CLOSING,
|
||||
// The connection has been closed
|
||||
STATE_CLOSED,
|
||||
// An error has occured
|
||||
STATE_ERROR
|
||||
} _connectionState;
|
||||
|
||||
enum {
|
||||
CSTATE_UNDEFINED,
|
||||
CSTATE_ACTIVE,
|
||||
CSTATE_CLOSED
|
||||
} _clientState;
|
||||
|
||||
private:
|
||||
void raiseError(uint16_t code, std::string reason);
|
||||
void readLine(int lengthLimit);
|
||||
|
||||
bool isTimeoutExceeded();
|
||||
void refreshTimeout();
|
||||
|
||||
int updateBuffer();
|
||||
size_t pendingBufferSize();
|
||||
|
||||
void signalClientClose();
|
||||
void signalRequestError();
|
||||
size_t readBuffer(byte* buffer, size_t length);
|
||||
size_t getCacheSize();
|
||||
bool checkWebsocket();
|
||||
|
||||
// The receive buffer
|
||||
char _receiveBuffer[HTTPS_CONNECTION_DATA_CHUNK_SIZE];
|
||||
|
||||
// First index on _receive_buffer that has not been processed yet (anything before may be discarded)
|
||||
int _bufferProcessed;
|
||||
// The index on the receive_buffer that is the first one which is empty at the end.
|
||||
int _bufferUnusedIdx;
|
||||
|
||||
// Socket address, length etc for the connection
|
||||
struct sockaddr _sockAddr;
|
||||
socklen_t _addrLen;
|
||||
int _socket;
|
||||
|
||||
// Resource resolver used to resolve resources
|
||||
ResourceResolver * _resResolver;
|
||||
|
||||
// The parser line. The struct is used to read the next line up to the \r\n in readLine()
|
||||
struct {
|
||||
std::string text = "";
|
||||
bool parsingFinished = false;
|
||||
} _parserLine;
|
||||
|
||||
// HTTP properties: Method, Request, Headers
|
||||
std::string _httpMethod;
|
||||
std::string _httpResource;
|
||||
HTTPHeaders * _httpHeaders;
|
||||
|
||||
// Default headers that are applied to every response
|
||||
HTTPHeaders * _defaultHeaders;
|
||||
|
||||
// Should we use keep alive
|
||||
bool _isKeepAlive;
|
||||
|
||||
//Websocket connection
|
||||
WebsocketHandler * _wsHandler;
|
||||
|
||||
};
|
||||
|
||||
void handleWebsocketHandshake(HTTPRequest * req, HTTPResponse * res);
|
||||
|
||||
std::string websocketKeyResponseHash(std::string const &key);
|
||||
|
||||
void validationMiddleware(HTTPRequest * req, HTTPResponse * res, std::function<void()> next);
|
||||
|
||||
} /* namespace httpsserver */
|
||||
|
||||
#endif /* SRC_HTTPCONNECTION_HPP_ */
|
43
src/http/HTTPHeader.cpp
Normal file
43
src/http/HTTPHeader.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
#include "HTTPHeader.hpp"
|
||||
|
||||
#include <locale>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
HTTPHeader::HTTPHeader(const std::string &name, const std::string &value):
|
||||
_name(normalizeHeaderName(name)),
|
||||
_value(value) {
|
||||
|
||||
}
|
||||
|
||||
HTTPHeader::~HTTPHeader() {
|
||||
|
||||
}
|
||||
|
||||
std::string HTTPHeader::print() {
|
||||
return _name + ": " + _value;
|
||||
}
|
||||
|
||||
std::string normalizeHeaderName(std::string const &name) {
|
||||
std::locale loc;
|
||||
std::stringbuf buf;
|
||||
std::ostream oBuf(&buf);
|
||||
bool upper = true;
|
||||
std::string::size_type len = name.length();
|
||||
for (std::string::size_type i = 0; i < len; ++i) {
|
||||
if (upper) {
|
||||
oBuf << std::toupper(name[i], loc);
|
||||
upper = false;
|
||||
} else {
|
||||
oBuf << std::tolower(name[i], loc);
|
||||
if (!std::isalnum(name[i], loc)) {
|
||||
upper=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.str();
|
||||
}
|
||||
|
||||
} /* namespace httpsserver */
|
32
src/http/HTTPHeader.hpp
Normal file
32
src/http/HTTPHeader.hpp
Normal file
@ -0,0 +1,32 @@
|
||||
#ifndef SRC_HTTPHEADER_HPP_
|
||||
#define SRC_HTTPHEADER_HPP_
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
/**
|
||||
* \brief Represents a single name/value pair of an HTTP header
|
||||
*/
|
||||
class HTTPHeader {
|
||||
public:
|
||||
HTTPHeader(const std::string &name, const std::string &value);
|
||||
virtual ~HTTPHeader();
|
||||
const std::string _name;
|
||||
const std::string _value;
|
||||
std::string print();
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Normalizes case in header names
|
||||
*
|
||||
* It converts the first letter and every letter after a non-alnum character
|
||||
* to uppercase. For example, "content-length" becomes "Content-Length" and
|
||||
* "HOST" becomes "Host".
|
||||
*/
|
||||
std::string normalizeHeaderName(std::string const &name);
|
||||
|
||||
} /* namespace httpsserver */
|
||||
|
||||
#endif /* SRC_HTTPHEADER_HPP_ */
|
61
src/http/HTTPHeaders.cpp
Normal file
61
src/http/HTTPHeaders.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
#include "HTTPHeaders.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
HTTPHeaders::HTTPHeaders() {
|
||||
_headers = new std::vector<HTTPHeader *>();
|
||||
|
||||
}
|
||||
|
||||
HTTPHeaders::~HTTPHeaders() {
|
||||
clearAll();
|
||||
delete _headers;
|
||||
}
|
||||
|
||||
HTTPHeader * HTTPHeaders::get(std::string const &name) {
|
||||
std::string normalizedName = normalizeHeaderName(name);
|
||||
for(std::vector<HTTPHeader*>::iterator header = _headers->begin(); header != _headers->end(); ++header) {
|
||||
if ((*header)->_name.compare(normalizedName)==0) {
|
||||
return (*header);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
std::string HTTPHeaders::getValue(std::string const &name) {
|
||||
std::string normalizedName = normalizeHeaderName(name);
|
||||
for(std::vector<HTTPHeader*>::iterator header = _headers->begin(); header != _headers->end(); ++header) {
|
||||
if ((*header)->_name.compare(normalizedName)==0) {
|
||||
return ((*header)->_value);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
void HTTPHeaders::set(HTTPHeader * header) {
|
||||
for(int i = 0; i < _headers->size(); i++) {
|
||||
if ((*_headers)[i]->_name.compare(header->_name)==0) {
|
||||
delete (*_headers)[i];
|
||||
(*_headers)[i] = header;
|
||||
return;
|
||||
}
|
||||
}
|
||||
_headers->push_back(header);
|
||||
}
|
||||
|
||||
std::vector<HTTPHeader *> * HTTPHeaders::getAll() {
|
||||
return _headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all headers
|
||||
*/
|
||||
void HTTPHeaders::clearAll() {
|
||||
for(std::vector<HTTPHeader*>::iterator header = _headers->begin(); header != _headers->end(); ++header) {
|
||||
delete (*header);
|
||||
}
|
||||
_headers->clear();
|
||||
}
|
||||
|
||||
} /* namespace httpsserver */
|
37
src/http/HTTPHeaders.hpp
Normal file
37
src/http/HTTPHeaders.hpp
Normal file
@ -0,0 +1,37 @@
|
||||
#ifndef SRC_HTTPHEADERS_HPP_
|
||||
#define SRC_HTTPHEADERS_HPP_
|
||||
|
||||
#include <string>
|
||||
// Arduino declares it's own min max, incompatible with the stl...
|
||||
#undef min
|
||||
#undef max
|
||||
#include <vector>
|
||||
|
||||
#include "HTTPSServerConstants.hpp"
|
||||
#include "HTTPHeader.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
/**
|
||||
* \brief Groups and manages a set of HTTPHeader instances
|
||||
*/
|
||||
class HTTPHeaders {
|
||||
public:
|
||||
HTTPHeaders();
|
||||
virtual ~HTTPHeaders();
|
||||
|
||||
HTTPHeader * get(std::string const &name);
|
||||
std::string getValue(std::string const &name);
|
||||
void set(HTTPHeader * header);
|
||||
|
||||
std::vector<HTTPHeader *> * getAll();
|
||||
|
||||
void clearAll();
|
||||
|
||||
private:
|
||||
std::vector<HTTPHeader*> * _headers;
|
||||
};
|
||||
|
||||
} /* namespace httpsserver */
|
||||
|
||||
#endif /* SRC_HTTPHEADERS_HPP_ */
|
26
src/http/HTTPMiddlewareFunction.hpp
Normal file
26
src/http/HTTPMiddlewareFunction.hpp
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef SRC_HTTPMIDDLEWAREFUNCTION_HPP_
|
||||
#define SRC_HTTPMIDDLEWAREFUNCTION_HPP_
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "HTTPRequest.hpp"
|
||||
#include "HTTPResponse.hpp"
|
||||
#include "HTTPSCallbackFunction.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
class HTTPRequest;
|
||||
/**
|
||||
* \brief A middleware function that can be registered at the server.
|
||||
*
|
||||
* It will be called before an incoming request is passed to any HTTPSCallbackFunction and may perform
|
||||
* operations like redirects or authentication.
|
||||
*
|
||||
* It receives the request and response object as well as a function pointer ("next") to pass on processing.
|
||||
* This allows chaining those functions. If next() is not called, the HTTPSCallbackFunction that
|
||||
* would match the request url will not be invoked. This might become handy if you want to intercept request
|
||||
* handling in case of missing authentication. Don't forget to call next in case you want to access your
|
||||
* resources, though.
|
||||
*/
|
||||
typedef void (HTTPSMiddlewareFunction)(HTTPRequest * req, HTTPResponse * res, std::function<void()> next);
|
||||
}
|
||||
#endif /* SRC_HTTPMIDDLEWAREFUNCTION_HPP_ */
|
288
src/http/HTTPMultipartBodyParser.cpp
Normal file
288
src/http/HTTPMultipartBodyParser.cpp
Normal file
@ -0,0 +1,288 @@
|
||||
#include "HTTPMultipartBodyParser.hpp"
|
||||
#include <sstream>
|
||||
|
||||
const size_t MAXLINESIZE = 256;
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
HTTPMultipartBodyParser::HTTPMultipartBodyParser(HTTPRequest * req):
|
||||
HTTPBodyParser(req),
|
||||
peekBuffer(NULL),
|
||||
peekBufferSize(0),
|
||||
boundary(""),
|
||||
lastBoundary(""),
|
||||
fieldName(""),
|
||||
fieldMimeType(""),
|
||||
fieldFilename("")
|
||||
{
|
||||
auto contentType = _request->getHeader("Content-Type");
|
||||
#ifdef DEBUG_MULTIPART_PARSER
|
||||
Serial.print("Content type: ");
|
||||
Serial.println(contentType.c_str());
|
||||
#endif
|
||||
auto boundaryIndex = contentType.find("boundary=");
|
||||
if(boundaryIndex == std::string::npos) {
|
||||
HTTPS_LOGE("Multipart: missing boundary=");
|
||||
discardBody();
|
||||
return;
|
||||
}
|
||||
boundary = contentType.substr(boundaryIndex + 9); // "boundary="
|
||||
auto commaIndex = boundary.find(';');
|
||||
boundary = "--" + boundary.substr(0, commaIndex);
|
||||
if(boundary.size() > 72) {
|
||||
HTTPS_LOGE("Multipart: boundary string too long");
|
||||
discardBody();
|
||||
}
|
||||
lastBoundary = boundary + "--";
|
||||
}
|
||||
|
||||
HTTPMultipartBodyParser::~HTTPMultipartBodyParser() {
|
||||
if (peekBuffer) {
|
||||
free(peekBuffer);
|
||||
peekBuffer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPMultipartBodyParser::discardBody() {
|
||||
if (peekBuffer) {
|
||||
free(peekBuffer);
|
||||
}
|
||||
peekBuffer = NULL;
|
||||
peekBufferSize = 0;
|
||||
_request->discardRequestBody();
|
||||
}
|
||||
|
||||
bool HTTPMultipartBodyParser::endOfBody() {
|
||||
return peekBufferSize == 0 && _request->requestComplete();
|
||||
}
|
||||
|
||||
void HTTPMultipartBodyParser::fillBuffer(size_t maxLen) {
|
||||
// Fill the buffer with up to maxLen bytes (total length, including
|
||||
// what was already in the buffer), but stop reading ahead once
|
||||
// we have a CR in the buffer (because the upper layers will
|
||||
// stop consuming there anyway, to forestall overrunning
|
||||
// a boundary)
|
||||
char *bufPtr;
|
||||
if (peekBuffer == NULL) {
|
||||
// Nothing in the buffer. Allocate one of the wanted size
|
||||
peekBuffer = (char *)malloc(maxLen);
|
||||
if (peekBuffer == NULL) {
|
||||
HTTPS_LOGE("Multipart: out of memory");
|
||||
discardBody();
|
||||
return;
|
||||
}
|
||||
bufPtr = peekBuffer;
|
||||
peekBufferSize = 0;
|
||||
} else if (peekBufferSize < maxLen) {
|
||||
// Something in the buffer, but not enough
|
||||
char *newPeekBuffer = (char *)realloc(peekBuffer, maxLen);
|
||||
if (newPeekBuffer == NULL) {
|
||||
HTTPS_LOGE("Multipart: out of memory");
|
||||
discardBody();
|
||||
return;
|
||||
}
|
||||
peekBuffer = newPeekBuffer;
|
||||
bufPtr = peekBuffer + peekBufferSize;
|
||||
} else {
|
||||
// We already have enough data in the buffer.
|
||||
return;
|
||||
}
|
||||
while(bufPtr < peekBuffer+maxLen) {
|
||||
size_t didRead = _request->readChars(bufPtr, peekBuffer+maxLen-bufPtr);
|
||||
if (didRead == 0) {
|
||||
break;
|
||||
}
|
||||
bufPtr += didRead;
|
||||
// We stop buffering once we have a CR in the buffer
|
||||
if (memchr(peekBuffer, '\r', bufPtr-peekBuffer) != NULL) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
peekBufferSize = bufPtr - peekBuffer;
|
||||
if (peekBufferSize == 0) {
|
||||
HTTPS_LOGE("Multipart incomplete");
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPMultipartBodyParser::consumedBuffer(size_t consumed) {
|
||||
if (consumed == 0) {
|
||||
return;
|
||||
}
|
||||
if (consumed == peekBufferSize) {
|
||||
free(peekBuffer);
|
||||
peekBuffer = NULL;
|
||||
peekBufferSize = 0;
|
||||
} else {
|
||||
memmove(peekBuffer, peekBuffer+consumed, peekBufferSize-consumed);
|
||||
peekBufferSize -= consumed;
|
||||
}
|
||||
}
|
||||
|
||||
bool HTTPMultipartBodyParser::skipCRLF() {
|
||||
if (peekBufferSize < 2) {
|
||||
fillBuffer(2);
|
||||
}
|
||||
if (peekBufferSize < 2) {
|
||||
return false;
|
||||
}
|
||||
if (peekBuffer[0] != '\r') {
|
||||
return false;
|
||||
}
|
||||
if (peekBuffer[1] != '\n') {
|
||||
HTTPS_LOGE("Multipart incorrect line terminator");
|
||||
discardBody();
|
||||
return false;
|
||||
}
|
||||
consumedBuffer(2);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string HTTPMultipartBodyParser::readLine() {
|
||||
fillBuffer(MAXLINESIZE);
|
||||
if (peekBufferSize == 0) {
|
||||
return "";
|
||||
}
|
||||
char *crPtr = (char *)memchr(peekBuffer, '\r', peekBufferSize);
|
||||
if (crPtr == NULL) {
|
||||
HTTPS_LOGE("Multipart line too long");
|
||||
discardBody();
|
||||
return "";
|
||||
}
|
||||
size_t lineLength = crPtr-peekBuffer;
|
||||
std::string rv(peekBuffer, lineLength);
|
||||
consumedBuffer(lineLength);
|
||||
skipCRLF();
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Returns true if the buffer contains a boundary (or possibly lastBoundary)
|
||||
bool HTTPMultipartBodyParser::peekBoundary() {
|
||||
if (peekBuffer == NULL || peekBufferSize < boundary.size()) {
|
||||
return false;
|
||||
}
|
||||
char *ptr = peekBuffer;
|
||||
if (*ptr == '\r') {
|
||||
ptr++;
|
||||
}
|
||||
if (*ptr == '\n') {
|
||||
ptr++;
|
||||
}
|
||||
return memcmp(ptr, boundary.c_str(), boundary.size()) == 0;
|
||||
}
|
||||
|
||||
bool HTTPMultipartBodyParser::nextField() {
|
||||
fillBuffer(MAXLINESIZE);
|
||||
while(!peekBoundary()) {
|
||||
std::string dummy = readLine();
|
||||
if (endOfBody()) {
|
||||
HTTPS_LOGE("Multipart missing last boundary");
|
||||
return false;
|
||||
}
|
||||
fillBuffer(MAXLINESIZE);
|
||||
}
|
||||
skipCRLF();
|
||||
std::string line = readLine();
|
||||
if (line == lastBoundary) {
|
||||
discardBody();
|
||||
return false;
|
||||
}
|
||||
if (line != boundary) {
|
||||
HTTPS_LOGE("Multipart incorrect boundary");
|
||||
return false;
|
||||
}
|
||||
// Read header lines up to and including blank line
|
||||
fieldName = "";
|
||||
fieldMimeType = "text/plain";
|
||||
fieldFilename = "";
|
||||
while (true) {
|
||||
line = readLine();
|
||||
if (line == "") {
|
||||
break;
|
||||
}
|
||||
if (line.substr(0, 14) == "Content-Type: ") {
|
||||
fieldMimeType = line.substr(14);
|
||||
}
|
||||
if (line.substr(0, 31) == "Content-Disposition: form-data;") {
|
||||
// Parse name=value; or name="value"; fields.
|
||||
std::string field;
|
||||
line = line.substr(31);
|
||||
while(true) {
|
||||
size_t pos = line.find_first_not_of(' ');
|
||||
if (pos != std::string::npos) {
|
||||
line = line.substr(pos);
|
||||
}
|
||||
if (line == "") break;
|
||||
pos = line.find(';');
|
||||
if (pos == std::string::npos) {
|
||||
field = line;
|
||||
line = "";
|
||||
} else {
|
||||
field = line.substr(0, pos);
|
||||
line = line.substr(pos+1);
|
||||
}
|
||||
pos = field.find('=');
|
||||
if (pos == std::string::npos) {
|
||||
HTTPS_LOGE("Multipart ill-formed form-data header");
|
||||
return false;
|
||||
}
|
||||
std::string headerName = field.substr(0, pos);
|
||||
std::string headerValue = field.substr(pos+1);
|
||||
if (headerValue.substr(0,1) == "\"") {
|
||||
headerValue = headerValue.substr(1, headerValue.size()-2);
|
||||
}
|
||||
if (headerName == "name") {
|
||||
fieldName = headerValue;
|
||||
}
|
||||
if (headerName == "filename") {
|
||||
fieldFilename = headerValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fieldName == "") {
|
||||
HTTPS_LOGE("Multipart missing name");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string HTTPMultipartBodyParser::getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
std::string HTTPMultipartBodyParser::getFieldFilename() {
|
||||
return fieldFilename;
|
||||
}
|
||||
|
||||
std::string HTTPMultipartBodyParser::getFieldMimeType() {
|
||||
return fieldMimeType;
|
||||
}
|
||||
|
||||
bool HTTPMultipartBodyParser::endOfField() {
|
||||
return peekBoundary();
|
||||
}
|
||||
|
||||
size_t HTTPMultipartBodyParser::read(byte* buffer, size_t bufferSize) {
|
||||
if (peekBoundary()) {
|
||||
return 0;
|
||||
}
|
||||
size_t readSize = std::min(bufferSize, MAXLINESIZE);
|
||||
fillBuffer(readSize);
|
||||
if (peekBoundary()) {
|
||||
return 0;
|
||||
}
|
||||
// We read at most up to a CR (so we don't miss a boundary that has been partially buffered)
|
||||
// but we always read at least one byte so if the first byte in the buffer is a CR we do read it.
|
||||
if (peekBufferSize > 1) {
|
||||
char *crPtr = (char *)memchr(peekBuffer+1, '\r', peekBufferSize-1);
|
||||
if (crPtr != NULL && crPtr - peekBuffer < bufferSize) {
|
||||
bufferSize = crPtr - peekBuffer;
|
||||
}
|
||||
}
|
||||
size_t copySize = std::min(bufferSize, peekBufferSize);
|
||||
memcpy(buffer, peekBuffer, copySize);
|
||||
consumedBuffer(copySize);
|
||||
return copySize;
|
||||
}
|
||||
|
||||
} /* namespace httpsserver */
|
39
src/http/HTTPMultipartBodyParser.hpp
Normal file
39
src/http/HTTPMultipartBodyParser.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
#ifndef SRC_HTTPMULTIPARTBODYPARSER_HPP_
|
||||
#define SRC_HTTPMULTIPARTBODYPARSER_HPP_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "HTTPBodyParser.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
class HTTPMultipartBodyParser : public HTTPBodyParser {
|
||||
public:
|
||||
HTTPMultipartBodyParser(HTTPRequest * req);
|
||||
~HTTPMultipartBodyParser();
|
||||
virtual bool nextField();
|
||||
virtual std::string getFieldName();
|
||||
virtual std::string getFieldFilename();
|
||||
virtual std::string getFieldMimeType();
|
||||
virtual bool endOfField();
|
||||
virtual size_t read(byte* buffer, size_t bufferSize);
|
||||
private:
|
||||
std::string readLine();
|
||||
void fillBuffer(size_t maxLen);
|
||||
void consumedBuffer(size_t consumed);
|
||||
bool skipCRLF();
|
||||
bool peekBoundary();
|
||||
void discardBody();
|
||||
bool endOfBody();
|
||||
char *peekBuffer;
|
||||
size_t peekBufferSize;
|
||||
|
||||
std::string boundary;
|
||||
std::string lastBoundary;
|
||||
std::string fieldName;
|
||||
std::string fieldMimeType;
|
||||
std::string fieldFilename;
|
||||
};
|
||||
|
||||
} // namespace httpserver
|
||||
|
||||
#endif
|
54
src/http/HTTPNode.cpp
Normal file
54
src/http/HTTPNode.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
#include "HTTPNode.hpp"
|
||||
#include "HTTPSServerConstants.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
HTTPNode::HTTPNode(std::string const &path, const HTTPNodeType nodeType, std::string const &tag):
|
||||
_path(path),
|
||||
_tag(tag),
|
||||
_nodeType(nodeType) {
|
||||
|
||||
// Count the parameters and store the indices
|
||||
size_t idx = 0;
|
||||
while((idx = path.find("/*", idx)) != std::string::npos) {
|
||||
// Skip the "/*"
|
||||
idx+=2;
|
||||
// Assure that we have either /* at the URL's end or /*/ somewhere in between
|
||||
if (idx == path.size() || path[idx] == '/') {
|
||||
_pathParamIdx.push_back(idx - 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
HTTPNode::~HTTPNode() {
|
||||
// Delete validator references
|
||||
for(std::vector<HTTPValidator*>::iterator validator = _validators.begin(); validator != _validators.end(); ++validator) {
|
||||
delete *validator;
|
||||
}
|
||||
}
|
||||
|
||||
bool HTTPNode::hasPathParameter() {
|
||||
return !_pathParamIdx.empty();
|
||||
}
|
||||
|
||||
ssize_t HTTPNode::getParamIdx(size_t idx) {
|
||||
if (idx<_pathParamIdx.size()) {
|
||||
return _pathParamIdx[idx];
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
size_t HTTPNode::getPathParamCount() {
|
||||
return _pathParamIdx.size();
|
||||
}
|
||||
|
||||
void HTTPNode::addPathParamValidator(size_t paramIdx, const HTTPValidationFunction * validator) {
|
||||
_validators.push_back(new HTTPValidator(paramIdx, validator));
|
||||
|
||||
}
|
||||
|
||||
std::vector<HTTPValidator*> * HTTPNode::getValidators() {
|
||||
return &_validators;
|
||||
}
|
||||
}
|
70
src/http/HTTPNode.hpp
Normal file
70
src/http/HTTPNode.hpp
Normal file
@ -0,0 +1,70 @@
|
||||
#ifndef SRC_HTTPNODE_HPP_
|
||||
#define SRC_HTTPNODE_HPP_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <string>
|
||||
#undef min
|
||||
#undef max
|
||||
#include <vector>
|
||||
#include "HTTPValidator.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
enum HTTPNodeType {
|
||||
/** Node with a handler callback function (class ResourceNode) */
|
||||
HANDLER_CALLBACK,
|
||||
/** Node with a websocket handler (class WebsocketNode) */
|
||||
WEBSOCKET
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Base class for a URL/route-handler in the server.
|
||||
*
|
||||
* Use ResourceNode for requests that access dynamic or static resources or HttpNode for routes that
|
||||
* create Websockets.
|
||||
*/
|
||||
class HTTPNode {
|
||||
public:
|
||||
HTTPNode(const std::string &path, const HTTPNodeType nodeType, const std::string &tag = "");
|
||||
virtual ~HTTPNode();
|
||||
|
||||
/**
|
||||
* The path under which this node will be available. Should start with a slash. Example:
|
||||
* "/myResource"
|
||||
*/
|
||||
const std::string _path;
|
||||
|
||||
/**
|
||||
* Stores a tag that can be used in middleware and handler functions to identify this
|
||||
* specific node, tag the node with a required permission, ...
|
||||
*/
|
||||
const std::string _tag;
|
||||
|
||||
/** Stores the type of the node (as we have not runtime type information by default) */
|
||||
const HTTPNodeType _nodeType;
|
||||
|
||||
bool hasPathParameter();
|
||||
size_t getPathParamCount();
|
||||
ssize_t getParamIdx(size_t);
|
||||
|
||||
std::vector<HTTPValidator*> * getValidators();
|
||||
|
||||
virtual std::string getMethod() = 0;
|
||||
|
||||
/**
|
||||
* Adds a validation function that checks if the actual value of a parameter matches the expectation
|
||||
* @param paramIdx defines the ID of the parameter that should be checked (starts by 0)
|
||||
* @param validator the function (string -> bool) that checks if the parameter matches the expecatation
|
||||
*
|
||||
* @see ValidatorFunctions.hpp if you need some predefined templates for functions
|
||||
*/
|
||||
void addPathParamValidator(size_t paramIdx, const HTTPValidationFunction * validator);
|
||||
|
||||
private:
|
||||
std::vector<size_t> _pathParamIdx;
|
||||
std::vector<HTTPValidator*> _validators;
|
||||
};
|
||||
|
||||
} // namespace httpserver
|
||||
|
||||
#endif
|
185
src/http/HTTPRequest.cpp
Normal file
185
src/http/HTTPRequest.cpp
Normal file
@ -0,0 +1,185 @@
|
||||
#include "HTTPRequest.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
HTTPRequest::HTTPRequest(
|
||||
ConnectionContext * con,
|
||||
HTTPHeaders * headers,
|
||||
HTTPNode * resolvedNode,
|
||||
std::string method,
|
||||
ResourceParameters * params,
|
||||
std::string requestString):
|
||||
_con(con),
|
||||
_headers(headers),
|
||||
_resolvedNode(resolvedNode),
|
||||
_method(method),
|
||||
_params(params),
|
||||
_requestString(requestString) {
|
||||
|
||||
HTTPHeader * contentLength = headers->get("Content-Length");
|
||||
if (contentLength == NULL) {
|
||||
_remainingContent = 0;
|
||||
_contentLengthSet = false;
|
||||
} else {
|
||||
_remainingContent = parseInt(contentLength->_value);
|
||||
_contentLengthSet = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
HTTPRequest::~HTTPRequest() {
|
||||
_headers->clearAll();
|
||||
}
|
||||
|
||||
|
||||
ResourceParameters * HTTPRequest::getParams() {
|
||||
return _params;
|
||||
}
|
||||
|
||||
HTTPHeaders * HTTPRequest::getHTTPHeaders() {
|
||||
return _headers;
|
||||
}
|
||||
|
||||
std::string HTTPRequest::getHeader(std::string const &name) {
|
||||
HTTPHeader * h = _headers->get(name);
|
||||
if (h != NULL) {
|
||||
return h->_value;
|
||||
} else {
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPRequest::setHeader(std::string const &name, std::string const &value) {
|
||||
_headers->set(new HTTPHeader(name, value));
|
||||
}
|
||||
|
||||
HTTPNode * HTTPRequest::getResolvedNode() {
|
||||
return _resolvedNode;
|
||||
}
|
||||
|
||||
IPAddress HTTPRequest::getClientIP() {
|
||||
return _con->getClientIP();
|
||||
}
|
||||
|
||||
size_t HTTPRequest::readBytes(byte * buffer, size_t length) {
|
||||
|
||||
// Limit reading to content length
|
||||
if (_contentLengthSet && length > _remainingContent) {
|
||||
length = _remainingContent;
|
||||
}
|
||||
|
||||
size_t bytesRead = 0;
|
||||
if (length > 0) {
|
||||
bytesRead = _con->readBuffer(buffer, length);
|
||||
}
|
||||
|
||||
if (_contentLengthSet) {
|
||||
_remainingContent -= bytesRead;
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
size_t HTTPRequest::readChars(char * buffer, size_t length) {
|
||||
return readBytes((byte*)buffer, length);
|
||||
}
|
||||
|
||||
size_t HTTPRequest::getContentLength() {
|
||||
return _remainingContent;
|
||||
}
|
||||
|
||||
std::string HTTPRequest::getRequestString() {
|
||||
return _requestString;
|
||||
}
|
||||
|
||||
std::string HTTPRequest::getMethod() {
|
||||
return _method;
|
||||
}
|
||||
|
||||
std::string HTTPRequest::getTag() {
|
||||
return _resolvedNode->_tag;
|
||||
}
|
||||
|
||||
bool HTTPRequest::requestComplete() {
|
||||
if (_contentLengthSet) {
|
||||
// If we have a content size, rely on it.
|
||||
return (_remainingContent == 0);
|
||||
} else {
|
||||
// If there is no more input...
|
||||
return (_con->pendingBufferSize() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will drop whatever is remaining of the request body
|
||||
*/
|
||||
void HTTPRequest::discardRequestBody() {
|
||||
byte buf[16];
|
||||
while(!requestComplete()) {
|
||||
readBytes(buf, 16);
|
||||
}
|
||||
}
|
||||
|
||||
std::string HTTPRequest::getBasicAuthUser() {
|
||||
std::string token = decodeBasicAuthToken();
|
||||
size_t splitpoint = token.find(":");
|
||||
if (splitpoint != std::string::npos && splitpoint > 0) {
|
||||
return token.substr(0, splitpoint);
|
||||
} else {
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
std::string HTTPRequest::getBasicAuthPassword() {
|
||||
std::string token = decodeBasicAuthToken();
|
||||
size_t splitpoint = token.find(":");
|
||||
if (splitpoint != std::string::npos && splitpoint > 0) {
|
||||
return token.substr(splitpoint+1);
|
||||
} else {
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
std::string HTTPRequest::decodeBasicAuthToken() {
|
||||
std::string basicAuthString = getHeader("Authorization");
|
||||
// Get the length of the token
|
||||
size_t sourceLength = basicAuthString.length();
|
||||
// Only handle basic auth tokens
|
||||
if (basicAuthString.substr(0, 6) != "Basic ") {
|
||||
return std::string();
|
||||
}
|
||||
// If the token is too long, skip
|
||||
if (sourceLength > 100) {
|
||||
return std::string();
|
||||
} else {
|
||||
// Try to decode. As we are using mbedtls anyway, we can use that function
|
||||
unsigned char * bufOut = new unsigned char[basicAuthString.length()];
|
||||
size_t outputLength = 0;
|
||||
int res = mbedtls_base64_decode(
|
||||
bufOut,
|
||||
sourceLength,
|
||||
&outputLength,
|
||||
((const unsigned char *)basicAuthString.substr(6).c_str()), // Strip "Basic "
|
||||
sourceLength - 6 // Strip "Basic "
|
||||
);
|
||||
// Failure of decoding
|
||||
if (res != 0) {
|
||||
delete[] bufOut;
|
||||
return std::string();
|
||||
}
|
||||
std::string tokenRes = std::string((char*)bufOut, outputLength);
|
||||
delete[] bufOut;
|
||||
return tokenRes;
|
||||
}
|
||||
}
|
||||
|
||||
bool HTTPRequest::isSecure() {
|
||||
return _con->isSecure();
|
||||
}
|
||||
|
||||
|
||||
void HTTPRequest::setWebsocketHandler(WebsocketHandler *wsHandler) {
|
||||
_con->setWebsocketHandler(wsHandler);
|
||||
}
|
||||
|
||||
} /* namespace httpsserver */
|
68
src/http/HTTPRequest.hpp
Normal file
68
src/http/HTTPRequest.hpp
Normal file
@ -0,0 +1,68 @@
|
||||
#ifndef SRC_HTTPREQUEST_HPP_
|
||||
#define SRC_HTTPREQUEST_HPP_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <IPAddress.h>
|
||||
#include <string>
|
||||
|
||||
#include <mbedtls/base64.h>
|
||||
|
||||
#include "ConnectionContext.hpp"
|
||||
#include "HTTPNode.hpp"
|
||||
#include "HTTPHeader.hpp"
|
||||
#include "HTTPHeaders.hpp"
|
||||
#include "ResourceParameters.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
/**
|
||||
* \brief Represents the request stream for an HTTP request
|
||||
*/
|
||||
class HTTPRequest {
|
||||
public:
|
||||
HTTPRequest(ConnectionContext * con, HTTPHeaders * headers, HTTPNode * resolvedNode, std::string method, ResourceParameters * params, std::string requestString);
|
||||
virtual ~HTTPRequest();
|
||||
|
||||
std::string getHeader(std::string const &name);
|
||||
void setHeader(std::string const &name, std::string const &value);
|
||||
HTTPNode * getResolvedNode();
|
||||
std::string getRequestString();
|
||||
std::string getMethod();
|
||||
std::string getTag();
|
||||
IPAddress getClientIP();
|
||||
|
||||
size_t readChars(char * buffer, size_t length);
|
||||
size_t readBytes(byte * buffer, size_t length);
|
||||
size_t getContentLength();
|
||||
bool requestComplete();
|
||||
void discardRequestBody();
|
||||
ResourceParameters * getParams();
|
||||
HTTPHeaders *getHTTPHeaders();
|
||||
std::string getBasicAuthUser();
|
||||
std::string getBasicAuthPassword();
|
||||
bool isSecure();
|
||||
void setWebsocketHandler(WebsocketHandler *wsHandler);
|
||||
|
||||
private:
|
||||
std::string decodeBasicAuthToken();
|
||||
|
||||
ConnectionContext * _con;
|
||||
|
||||
HTTPHeaders * _headers;
|
||||
|
||||
HTTPNode * _resolvedNode;
|
||||
|
||||
std::string _method;
|
||||
|
||||
ResourceParameters * _params;
|
||||
|
||||
std::string _requestString;
|
||||
|
||||
bool _contentLengthSet;
|
||||
size_t _remainingContent;
|
||||
};
|
||||
|
||||
} /* namespace httpsserver */
|
||||
|
||||
#endif /* SRC_HTTPREQUEST_HPP_ */
|
188
src/http/HTTPResponse.cpp
Normal file
188
src/http/HTTPResponse.cpp
Normal file
@ -0,0 +1,188 @@
|
||||
#include "HTTPResponse.hpp"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "lwip/sockets.h"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
HTTPResponse::HTTPResponse(ConnectionContext * con):
|
||||
_con(con) {
|
||||
|
||||
// Default status code is 200 OK
|
||||
_statusCode = 200;
|
||||
_statusText = "OK";
|
||||
_headerWritten = false;
|
||||
_isError = false;
|
||||
|
||||
_responseCacheSize = con->getCacheSize();
|
||||
_responseCachePointer = 0;
|
||||
if (_responseCacheSize > 0) {
|
||||
HTTPS_LOGD("Creating buffered response, size: %d", _responseCacheSize);
|
||||
_responseCache = new byte[_responseCacheSize];
|
||||
} else {
|
||||
HTTPS_LOGD("Creating non-buffered response");
|
||||
_responseCache = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
HTTPResponse::~HTTPResponse() {
|
||||
if (_responseCache != NULL) {
|
||||
delete[] _responseCache;
|
||||
}
|
||||
_headers.clearAll();
|
||||
}
|
||||
|
||||
void HTTPResponse::setStatusCode(uint16_t statusCode) {
|
||||
_statusCode = statusCode;
|
||||
}
|
||||
|
||||
void HTTPResponse::setStatusText(std::string const &statusText) {
|
||||
_statusText = statusText;
|
||||
}
|
||||
|
||||
uint16_t HTTPResponse::getStatusCode() {
|
||||
return _statusCode;
|
||||
}
|
||||
|
||||
std::string HTTPResponse::getStatusText() {
|
||||
return _statusText;
|
||||
}
|
||||
|
||||
void HTTPResponse::setHeader(std::string const &name, std::string const &value) {
|
||||
_headers.set(new HTTPHeader(name, value));
|
||||
}
|
||||
|
||||
std::string HTTPResponse::getHeader(std::string const &name) {
|
||||
HTTPHeader * h = _headers.get(name);
|
||||
if (h != NULL) {
|
||||
return h->_value;
|
||||
} else {
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
bool HTTPResponse::isHeaderWritten() {
|
||||
return _headerWritten;
|
||||
}
|
||||
|
||||
bool HTTPResponse::isResponseBuffered() {
|
||||
return _responseCache != NULL;
|
||||
}
|
||||
|
||||
void HTTPResponse::finalize() {
|
||||
if (isResponseBuffered()) {
|
||||
drainBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a string to the response. May be called several times.
|
||||
*/
|
||||
void HTTPResponse::printStd(const std::string &str) {
|
||||
write((uint8_t*)str.c_str(), str.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes bytes to the response. May be called several times.
|
||||
*/
|
||||
size_t HTTPResponse::write(const uint8_t *buffer, size_t size) {
|
||||
if(!isResponseBuffered()) {
|
||||
printHeader();
|
||||
}
|
||||
return writeBytesInternal(buffer, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a single byte to the response.
|
||||
*/
|
||||
size_t HTTPResponse::write(uint8_t b) {
|
||||
if(!isResponseBuffered()) {
|
||||
printHeader();
|
||||
}
|
||||
byte ba[] = {b};
|
||||
return writeBytesInternal(ba, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* If not already done, writes the header.
|
||||
*/
|
||||
void HTTPResponse::printHeader() {
|
||||
if (!_headerWritten) {
|
||||
HTTPS_LOGD("Printing headers");
|
||||
|
||||
// Status line, like: "HTTP/1.1 200 OK\r\n"
|
||||
std::string statusLine = "HTTP/1.1 " + intToString(_statusCode) + " " + _statusText + "\r\n";
|
||||
printInternal(statusLine, true);
|
||||
|
||||
// Each header, like: "Host: myEsp32\r\n"
|
||||
std::vector<HTTPHeader *> * headers = _headers.getAll();
|
||||
for(std::vector<HTTPHeader*>::iterator header = headers->begin(); header != headers->end(); ++header) {
|
||||
printInternal((*header)->print()+"\r\n", true);
|
||||
}
|
||||
printInternal("\r\n", true);
|
||||
|
||||
_headerWritten=true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be called to cancel the ongoing transmission and send the error page (if possible)
|
||||
*/
|
||||
void HTTPResponse::error() {
|
||||
_con->signalRequestError();
|
||||
}
|
||||
|
||||
void HTTPResponse::printInternal(const std::string &str, bool skipBuffer) {
|
||||
writeBytesInternal((uint8_t*)str.c_str(), str.length(), skipBuffer);
|
||||
}
|
||||
|
||||
size_t HTTPResponse::writeBytesInternal(const void * data, int length, bool skipBuffer) {
|
||||
if (!_isError) {
|
||||
if (isResponseBuffered() && !skipBuffer) {
|
||||
// We are buffering ...
|
||||
if(length <= _responseCacheSize - _responseCachePointer) {
|
||||
// ... and there is space left in the buffer -> Write to buffer
|
||||
size_t end = _responseCachePointer + length;
|
||||
size_t i = 0;
|
||||
while(_responseCachePointer < end) {
|
||||
_responseCache[_responseCachePointer++] = ((byte*)data)[i++];
|
||||
}
|
||||
// Returning skips the SSL_write below
|
||||
return length;
|
||||
} else {
|
||||
// .., and the buffer is too small. This is the point where we switch from
|
||||
// caching to streaming
|
||||
if (!_headerWritten) {
|
||||
setHeader("Connection", "close");
|
||||
}
|
||||
drainBuffer(true);
|
||||
}
|
||||
}
|
||||
|
||||
return _con->writeBuffer((byte*)data, length);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPResponse::drainBuffer(bool onOverflow) {
|
||||
if (!_headerWritten) {
|
||||
if (_responseCache != NULL && !onOverflow) {
|
||||
_headers.set(new HTTPHeader("Content-Length", intToString(_responseCachePointer)));
|
||||
}
|
||||
printHeader();
|
||||
}
|
||||
|
||||
if (_responseCache != NULL) {
|
||||
HTTPS_LOGD("Draining response buffer");
|
||||
// Check for 0 as it may be an overflow reaction without any data that has been written earlier
|
||||
if(_responseCachePointer > 0) {
|
||||
// FIXME: Return value?
|
||||
_con->writeBuffer((byte*)_responseCache, _responseCachePointer);
|
||||
}
|
||||
delete[] _responseCache;
|
||||
_responseCache = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace httpsserver */
|
71
src/http/HTTPResponse.hpp
Normal file
71
src/http/HTTPResponse.hpp
Normal file
@ -0,0 +1,71 @@
|
||||
#ifndef SRC_HTTPRESPONSE_HPP_
|
||||
#define SRC_HTTPRESPONSE_HPP_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <string>
|
||||
// Arduino declares it's own min max, incompatible with the stl...
|
||||
#undef min
|
||||
#undef max
|
||||
#undef write
|
||||
#include <vector>
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include "util.hpp"
|
||||
|
||||
#include "ConnectionContext.hpp"
|
||||
#include "HTTPHeaders.hpp"
|
||||
#include "HTTPHeader.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
/**
|
||||
* \brief Represents the response stream of an HTTP request
|
||||
*/
|
||||
class HTTPResponse : public Print {
|
||||
public:
|
||||
HTTPResponse(ConnectionContext * con);
|
||||
virtual ~HTTPResponse();
|
||||
|
||||
void setStatusCode(uint16_t statusCode);
|
||||
void setStatusText(std::string const &statusText);
|
||||
uint16_t getStatusCode();
|
||||
std::string getStatusText();
|
||||
void setHeader(std::string const &name, std::string const &value);
|
||||
std::string getHeader(std::string const &name);
|
||||
bool isHeaderWritten();
|
||||
|
||||
void printStd(std::string const &str);
|
||||
|
||||
// From Print:
|
||||
size_t write(const uint8_t *buffer, size_t size);
|
||||
size_t write(uint8_t);
|
||||
|
||||
void error();
|
||||
|
||||
bool isResponseBuffered();
|
||||
void finalize();
|
||||
|
||||
ConnectionContext * _con;
|
||||
|
||||
private:
|
||||
void printHeader();
|
||||
void printInternal(const std::string &str, bool skipBuffer = false);
|
||||
size_t writeBytesInternal(const void * data, int length, bool skipBuffer = false);
|
||||
void drainBuffer(bool onOverflow = false);
|
||||
|
||||
uint16_t _statusCode;
|
||||
std::string _statusText;
|
||||
HTTPHeaders _headers;
|
||||
bool _headerWritten;
|
||||
bool _isError;
|
||||
|
||||
// Response cache
|
||||
byte * _responseCache;
|
||||
size_t _responseCacheSize;
|
||||
size_t _responseCachePointer;
|
||||
};
|
||||
|
||||
} /* namespace httpsserver */
|
||||
|
||||
#endif /* SRC_HTTPRESPONSE_HPP_ */
|
14
src/http/HTTPSCallbackFunction.hpp
Normal file
14
src/http/HTTPSCallbackFunction.hpp
Normal file
@ -0,0 +1,14 @@
|
||||
#ifndef SRC_HTTPSCALLBACKFUNCTION_HPP_
|
||||
#define SRC_HTTPSCALLBACKFUNCTION_HPP_
|
||||
|
||||
#include "HTTPRequest.hpp"
|
||||
#include "HTTPResponse.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
/**
|
||||
* \brief A callback function that will be called by the server to handle a request
|
||||
*/
|
||||
typedef void (HTTPSCallbackFunction)(HTTPRequest * req, HTTPResponse * res);
|
||||
}
|
||||
|
||||
#endif /* SRC_HTTPSCALLBACKFUNCTION_HPP_ */
|
123
src/http/HTTPSConnection.cpp
Normal file
123
src/http/HTTPSConnection.cpp
Normal file
@ -0,0 +1,123 @@
|
||||
#include "HTTPSConnection.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
|
||||
HTTPSConnection::HTTPSConnection(ResourceResolver * resResolver):
|
||||
HTTPConnection(resResolver) {
|
||||
_ssl = NULL;
|
||||
}
|
||||
|
||||
HTTPSConnection::~HTTPSConnection() {
|
||||
// Close the socket
|
||||
closeConnection();
|
||||
}
|
||||
|
||||
bool HTTPSConnection::isSecure() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the connection from a server socket.
|
||||
*
|
||||
* The call WILL BLOCK if accept(serverSocketID) blocks. So use select() to check for that in advance.
|
||||
*/
|
||||
int HTTPSConnection::initialize(int serverSocketID, SSL_CTX * sslCtx, HTTPHeaders *defaultHeaders) {
|
||||
if (_connectionState == STATE_UNDEFINED) {
|
||||
// Let the base class connect the plain tcp socket
|
||||
int resSocket = HTTPConnection::initialize(serverSocketID, defaultHeaders);
|
||||
|
||||
// Build up SSL Connection context if the socket has been created successfully
|
||||
if (resSocket >= 0) {
|
||||
|
||||
_ssl = SSL_new(sslCtx);
|
||||
|
||||
if (_ssl) {
|
||||
// Bind SSL to the socket
|
||||
int success = SSL_set_fd(_ssl, resSocket);
|
||||
if (success) {
|
||||
|
||||
// Perform the handshake
|
||||
success = SSL_accept(_ssl);
|
||||
if (success) {
|
||||
return resSocket;
|
||||
} else {
|
||||
HTTPS_LOGE("SSL_accept failed. Aborting handshake. FID=%d", resSocket);
|
||||
}
|
||||
} else {
|
||||
HTTPS_LOGE("SSL_set_fd failed. Aborting handshake. FID=%d", resSocket);
|
||||
}
|
||||
} else {
|
||||
HTTPS_LOGE("SSL_new failed. Aborting handshake. FID=%d", resSocket);
|
||||
}
|
||||
|
||||
} else {
|
||||
HTTPS_LOGE("Could not accept() new connection. FID=%d", resSocket);
|
||||
}
|
||||
|
||||
_connectionState = STATE_ERROR;
|
||||
_clientState = CSTATE_ACTIVE;
|
||||
|
||||
// This will only be called if the connection could not be established and cleanup
|
||||
// variables like _ssl etc.
|
||||
closeConnection();
|
||||
}
|
||||
// Error: The connection has already been established or could not be established
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
void HTTPSConnection::closeConnection() {
|
||||
|
||||
// FIXME: Copy from HTTPConnection, could be done better probably
|
||||
if (_connectionState != STATE_ERROR && _connectionState != STATE_CLOSED) {
|
||||
|
||||
// First call to closeConnection - set the timestamp to calculate the timeout later on
|
||||
if (_connectionState != STATE_CLOSING) {
|
||||
_shutdownTS = millis();
|
||||
}
|
||||
|
||||
// Set the connection state to closing. We stay in closing as long as SSL has not been shutdown
|
||||
// correctly
|
||||
_connectionState = STATE_CLOSING;
|
||||
}
|
||||
|
||||
// Try to tear down SSL while we are in the _shutdownTS timeout period or if an error occurred
|
||||
if (_ssl) {
|
||||
if(_connectionState == STATE_ERROR || SSL_shutdown(_ssl) == 0) {
|
||||
// SSL_shutdown will return 1 as soon as the client answered with close notify
|
||||
// This means we are safe to close the socket
|
||||
SSL_free(_ssl);
|
||||
_ssl = NULL;
|
||||
} else if (_shutdownTS + HTTPS_SHUTDOWN_TIMEOUT < millis()) {
|
||||
// The timeout has been hit, we force SSL shutdown now by freeing the context
|
||||
SSL_free(_ssl);
|
||||
_ssl = NULL;
|
||||
HTTPS_LOGW("SSL_shutdown did not receive close notification from the client");
|
||||
_connectionState = STATE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
// If SSL has been brought down, close the socket
|
||||
if (!_ssl) {
|
||||
HTTPConnection::closeConnection();
|
||||
}
|
||||
}
|
||||
|
||||
size_t HTTPSConnection::writeBuffer(byte* buffer, size_t length) {
|
||||
return SSL_write(_ssl, buffer, length);
|
||||
}
|
||||
|
||||
size_t HTTPSConnection::readBytesToBuffer(byte* buffer, size_t length) {
|
||||
return SSL_read(_ssl, buffer, length);
|
||||
}
|
||||
|
||||
size_t HTTPSConnection::pendingByteCount() {
|
||||
return SSL_pending(_ssl);
|
||||
}
|
||||
|
||||
bool HTTPSConnection::canReadData() {
|
||||
return HTTPConnection::canReadData() || (SSL_pending(_ssl) > 0);
|
||||
}
|
||||
|
||||
} /* namespace httpsserver */
|
58
src/http/HTTPSConnection.hpp
Normal file
58
src/http/HTTPSConnection.hpp
Normal file
@ -0,0 +1,58 @@
|
||||
#ifndef SRC_HTTPSCONNECTION_HPP_
|
||||
#define SRC_HTTPSCONNECTION_HPP_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
// Required for SSL
|
||||
#include "openssl/ssl.h"
|
||||
#undef read
|
||||
|
||||
// Required for sockets
|
||||
#include "lwip/netdb.h"
|
||||
#undef read
|
||||
#include "lwip/sockets.h"
|
||||
|
||||
#include "HTTPSServerConstants.hpp"
|
||||
#include "HTTPConnection.hpp"
|
||||
#include "HTTPHeaders.hpp"
|
||||
#include "HTTPHeader.hpp"
|
||||
#include "ResourceResolver.hpp"
|
||||
#include "ResolvedResource.hpp"
|
||||
#include "ResourceNode.hpp"
|
||||
#include "HTTPRequest.hpp"
|
||||
#include "HTTPResponse.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
/**
|
||||
* \brief Connection class for an open TLS-enabled connection to an HTTPSServer
|
||||
*/
|
||||
class HTTPSConnection : public HTTPConnection {
|
||||
public:
|
||||
HTTPSConnection(ResourceResolver * resResolver);
|
||||
virtual ~HTTPSConnection();
|
||||
|
||||
virtual int initialize(int serverSocketID, SSL_CTX * sslCtx, HTTPHeaders *defaultHeaders);
|
||||
virtual void closeConnection();
|
||||
virtual bool isSecure();
|
||||
|
||||
protected:
|
||||
friend class HTTPRequest;
|
||||
friend class HTTPResponse;
|
||||
|
||||
virtual size_t readBytesToBuffer(byte* buffer, size_t length);
|
||||
virtual size_t pendingByteCount();
|
||||
virtual bool canReadData();
|
||||
virtual size_t writeBuffer(byte* buffer, size_t length);
|
||||
|
||||
private:
|
||||
// SSL context for this connection
|
||||
SSL * _ssl;
|
||||
|
||||
};
|
||||
|
||||
} /* namespace httpsserver */
|
||||
|
||||
#endif /* SRC_HTTPSCONNECTION_HPP_ */
|
102
src/http/HTTPSServer.cpp
Normal file
102
src/http/HTTPSServer.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
#include "HTTPSServer.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
|
||||
HTTPSServer::HTTPSServer(SSLCert * cert, const uint16_t port, const uint8_t maxConnections, const in_addr_t bindAddress):
|
||||
HTTPServer(port, maxConnections, bindAddress),
|
||||
_cert(cert) {
|
||||
|
||||
// Configure runtime data
|
||||
_sslctx = NULL;
|
||||
}
|
||||
|
||||
HTTPSServer::~HTTPSServer() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method starts the server and begins to listen on the port
|
||||
*/
|
||||
uint8_t HTTPSServer::setupSocket() {
|
||||
if (!isRunning()) {
|
||||
if (!setupSSLCTX()) {
|
||||
Serial.println("setupSSLCTX failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!setupCert()) {
|
||||
Serial.println("setupCert failed");
|
||||
SSL_CTX_free(_sslctx);
|
||||
_sslctx = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (HTTPServer::setupSocket()) {
|
||||
return 1;
|
||||
} else {
|
||||
Serial.println("setupSockets failed");
|
||||
SSL_CTX_free(_sslctx);
|
||||
_sslctx = NULL;
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPSServer::teardownSocket() {
|
||||
|
||||
HTTPServer::teardownSocket();
|
||||
|
||||
// Tear down the SSL context
|
||||
SSL_CTX_free(_sslctx);
|
||||
_sslctx = NULL;
|
||||
}
|
||||
|
||||
int HTTPSServer::createConnection(int idx) {
|
||||
HTTPSConnection * newConnection = new HTTPSConnection(this);
|
||||
_connections[idx] = newConnection;
|
||||
return newConnection->initialize(_socket, _sslctx, &_defaultHeaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method configures the ssl context that is used for the server
|
||||
*/
|
||||
uint8_t HTTPSServer::setupSSLCTX() {
|
||||
_sslctx = SSL_CTX_new(TLSv1_2_server_method());
|
||||
if (_sslctx) {
|
||||
// Set SSL Timeout to 5 minutes
|
||||
SSL_CTX_set_timeout(_sslctx, 300);
|
||||
return 1;
|
||||
} else {
|
||||
_sslctx = NULL;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method configures the certificate and private key for the given
|
||||
* ssl context
|
||||
*/
|
||||
uint8_t HTTPSServer::setupCert() {
|
||||
// Configure the certificate first
|
||||
uint8_t ret = SSL_CTX_use_certificate_ASN1(
|
||||
_sslctx,
|
||||
_cert->getCertLength(),
|
||||
_cert->getCertData()
|
||||
);
|
||||
|
||||
// Then set the private key accordingly
|
||||
if (ret) {
|
||||
ret = SSL_CTX_use_RSAPrivateKey_ASN1(
|
||||
_sslctx,
|
||||
_cert->getPKData(),
|
||||
_cert->getPKLength()
|
||||
);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} /* namespace httpsserver */
|
56
src/http/HTTPSServer.hpp
Normal file
56
src/http/HTTPSServer.hpp
Normal file
@ -0,0 +1,56 @@
|
||||
#ifndef SRC_HTTPSSERVER_HPP_
|
||||
#define SRC_HTTPSSERVER_HPP_
|
||||
|
||||
// Standard library
|
||||
#include <string>
|
||||
|
||||
// Arduino stuff
|
||||
#include <Arduino.h>
|
||||
|
||||
// Required for SSL
|
||||
#include "openssl/ssl.h"
|
||||
#undef read
|
||||
|
||||
// Internal includes
|
||||
#include "HTTPServer.hpp"
|
||||
#include "HTTPSServerConstants.hpp"
|
||||
#include "HTTPHeaders.hpp"
|
||||
#include "HTTPHeader.hpp"
|
||||
#include "ResourceNode.hpp"
|
||||
#include "ResourceResolver.hpp"
|
||||
#include "ResolvedResource.hpp"
|
||||
#include "HTTPSConnection.hpp"
|
||||
#include "SSLCert.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
/**
|
||||
* \brief Main implementation of the HTTP Server with TLS support. Use HTTPServer for plain HTTP
|
||||
*/
|
||||
class HTTPSServer : public HTTPServer {
|
||||
public:
|
||||
HTTPSServer(SSLCert * cert, const uint16_t portHTTPS = 443, const uint8_t maxConnections = 4, const in_addr_t bindAddress = 0);
|
||||
virtual ~HTTPSServer();
|
||||
|
||||
private:
|
||||
// Static configuration. Port, keys, etc. ====================
|
||||
// Certificate that should be used (includes private key)
|
||||
SSLCert * _cert;
|
||||
|
||||
//// Runtime data ============================================
|
||||
SSL_CTX * _sslctx;
|
||||
// Status of the server: Are we running, or not?
|
||||
|
||||
// Setup functions
|
||||
virtual uint8_t setupSocket();
|
||||
virtual void teardownSocket();
|
||||
uint8_t setupSSLCTX();
|
||||
uint8_t setupCert();
|
||||
|
||||
// Helper functions
|
||||
virtual int createConnection(int idx);
|
||||
};
|
||||
|
||||
} /* namespace httpsserver */
|
||||
|
||||
#endif /* SRC_HTTPSSERVER_HPP_ */
|
89
src/http/HTTPSServerConstants.hpp
Normal file
89
src/http/HTTPSServerConstants.hpp
Normal file
@ -0,0 +1,89 @@
|
||||
#ifndef SRC_HTTPSSERVERCONSTANTS_HPP_
|
||||
#define SRC_HTTPSSERVERCONSTANTS_HPP_
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
// 1: Error
|
||||
// 2: Error + Warn
|
||||
// 3: Error + Warn + Info
|
||||
// 4: Error + Warn + Info + Debug
|
||||
|
||||
#ifndef HTTPS_LOGLEVEL
|
||||
#define HTTPS_LOGLEVEL 3
|
||||
#endif
|
||||
|
||||
#ifdef HTTPS_LOGTIMESTAMP
|
||||
#define HTTPS_LOGTAG(LVL) Serial.printf("[HTTPS:" LVL ":%10lu] ", millis())
|
||||
#else
|
||||
#define HTTPS_LOGTAG(LVL) Serial.print("[HTTPS:" LVL "] ")
|
||||
#endif
|
||||
|
||||
#if HTTPS_LOGLEVEL > 0
|
||||
#define HTTPS_LOGE(...) HTTPS_LOGTAG("E");Serial.printf(__VA_ARGS__);Serial.println()
|
||||
#else
|
||||
#define HTTPS_LOGE(...) do {} while (0)
|
||||
#endif
|
||||
|
||||
#if HTTPS_LOGLEVEL > 1
|
||||
#define HTTPS_LOGW(...) HTTPS_LOGTAG("W");Serial.printf(__VA_ARGS__);Serial.println()
|
||||
#else
|
||||
#define HTTPS_LOGW(...) do {} while (0)
|
||||
#endif
|
||||
|
||||
#if HTTPS_LOGLEVEL > 2
|
||||
#define HTTPS_LOGI(...) HTTPS_LOGTAG("I");Serial.printf(__VA_ARGS__);Serial.println()
|
||||
#else
|
||||
#define HTTPS_LOGI(...) do {} while (0)
|
||||
#endif
|
||||
|
||||
#if HTTPS_LOGLEVEL > 3
|
||||
#define HTTPS_LOGD(...) HTTPS_LOGTAG("D");Serial.printf(__VA_ARGS__);Serial.println()
|
||||
#else
|
||||
#define HTTPS_LOGD(...) do {} while (0)
|
||||
#endif
|
||||
|
||||
// The following lines define limits of the protocol. Exceeding these limits will lead to a 500 error
|
||||
|
||||
// Maximum of header lines that are parsed
|
||||
#ifndef HTTPS_REQUEST_MAX_HEADERS
|
||||
#define HTTPS_REQUEST_MAX_HEADERS 20
|
||||
#endif
|
||||
|
||||
// Maximum length of the request line (GET /... HTTP/1.1)
|
||||
#ifndef HTTPS_REQUEST_MAX_REQUEST_LENGTH
|
||||
#define HTTPS_REQUEST_MAX_REQUEST_LENGTH 128
|
||||
#endif
|
||||
|
||||
// Maximum length of a header line (including name and value)
|
||||
#ifndef HTTPS_REQUEST_MAX_HEADER_LENGTH
|
||||
#define HTTPS_REQUEST_MAX_HEADER_LENGTH 384
|
||||
#endif
|
||||
|
||||
// Chunk size used for reading data from the ssl-enabled socket
|
||||
#ifndef HTTPS_CONNECTION_DATA_CHUNK_SIZE
|
||||
#define HTTPS_CONNECTION_DATA_CHUNK_SIZE 512
|
||||
#endif
|
||||
|
||||
// Size (in bytes) of the Connection:keep-alive Cache (we need to be able to
|
||||
// store-and-forward the response to calculate the content-size)
|
||||
#ifndef HTTPS_KEEPALIVE_CACHESIZE
|
||||
#define HTTPS_KEEPALIVE_CACHESIZE 1400
|
||||
#endif
|
||||
|
||||
// Timeout for an HTTPS connection without any transmission
|
||||
#ifndef HTTPS_CONNECTION_TIMEOUT
|
||||
#define HTTPS_CONNECTION_TIMEOUT 20000
|
||||
#endif
|
||||
|
||||
// Timeout used to wait for shutdown of SSL connection (ms)
|
||||
// (time for the client to return notify close flag) - without it, truncation attacks might be possible
|
||||
#ifndef HTTPS_SHUTDOWN_TIMEOUT
|
||||
#define HTTPS_SHUTDOWN_TIMEOUT 5000
|
||||
#endif
|
||||
|
||||
// Length of a SHA1 hash
|
||||
#ifndef HTTPS_SHA1_LENGTH
|
||||
#define HTTPS_SHA1_LENGTH 20
|
||||
#endif
|
||||
|
||||
#endif /* SRC_HTTPSSERVERCONSTANTS_HPP_ */
|
211
src/http/HTTPServer.cpp
Normal file
211
src/http/HTTPServer.cpp
Normal file
@ -0,0 +1,211 @@
|
||||
#include "HTTPServer.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
|
||||
HTTPServer::HTTPServer(const uint16_t port, const uint8_t maxConnections, const in_addr_t bindAddress):
|
||||
_port(port),
|
||||
_maxConnections(maxConnections),
|
||||
_bindAddress(bindAddress) {
|
||||
|
||||
// Create space for the connections
|
||||
_connections = new HTTPConnection*[maxConnections];
|
||||
for(uint8_t i = 0; i < maxConnections; i++) _connections[i] = NULL;
|
||||
|
||||
// Configure runtime data
|
||||
_socket = -1;
|
||||
_running = false;
|
||||
}
|
||||
|
||||
HTTPServer::~HTTPServer() {
|
||||
|
||||
// Stop the server.
|
||||
// This will also remove all existing connections
|
||||
if(_running) {
|
||||
stop();
|
||||
}
|
||||
|
||||
// Delete connection pointers
|
||||
delete[] _connections;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method starts the server and begins to listen on the port
|
||||
*/
|
||||
uint8_t HTTPServer::start() {
|
||||
if (!_running) {
|
||||
if (setupSocket()) {
|
||||
_running = true;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
bool HTTPServer::isRunning() {
|
||||
return _running;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method stops the server
|
||||
*/
|
||||
void HTTPServer::stop() {
|
||||
|
||||
if (_running) {
|
||||
// Set the flag that the server is stopped
|
||||
_running = false;
|
||||
|
||||
// Clean up the connections
|
||||
bool hasOpenConnections = true;
|
||||
while(hasOpenConnections) {
|
||||
hasOpenConnections = false;
|
||||
for(int i = 0; i < _maxConnections; i++) {
|
||||
if (_connections[i] != NULL) {
|
||||
_connections[i]->closeConnection();
|
||||
|
||||
// Check if closing succeeded. If not, we need to call the close function multiple times
|
||||
// and wait for the client
|
||||
if (_connections[i]->isClosed()) {
|
||||
delete _connections[i];
|
||||
_connections[i] = NULL;
|
||||
} else {
|
||||
hasOpenConnections = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
delay(1);
|
||||
}
|
||||
|
||||
teardownSocket();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a default header that is included in every response.
|
||||
*
|
||||
* This could be used for example to add a Server: header or for CORS options
|
||||
*/
|
||||
void HTTPServer::setDefaultHeader(std::string name, std::string value) {
|
||||
_defaultHeaders.set(new HTTPHeader(name, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* The loop method can either be called by periodical interrupt or in the main loop and handles processing
|
||||
* of data
|
||||
*/
|
||||
void HTTPServer::loop() {
|
||||
|
||||
// Only handle requests if the server is still running
|
||||
if(!_running) return;
|
||||
|
||||
// Step 1: Process existing connections
|
||||
// Process open connections and store the index of a free connection
|
||||
// (we might use that later on)
|
||||
int freeConnectionIdx = -1;
|
||||
for (int i = 0; i < _maxConnections; i++) {
|
||||
// Fetch a free index in the pointer array
|
||||
if (_connections[i] == NULL) {
|
||||
freeConnectionIdx = i;
|
||||
|
||||
} else {
|
||||
// if there is a connection (_connections[i]!=NULL), check if its open or closed:
|
||||
if (_connections[i]->isClosed()) {
|
||||
// if it's closed, clean up:
|
||||
delete _connections[i];
|
||||
_connections[i] = NULL;
|
||||
freeConnectionIdx = i;
|
||||
} else {
|
||||
// if not, process it:
|
||||
_connections[i]->loop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Check for new connections
|
||||
// This makes only sense if there is space to store the connection
|
||||
if (freeConnectionIdx > -1) {
|
||||
|
||||
// We create a file descriptor set to be able to use the select function
|
||||
fd_set sockfds;
|
||||
// Out socket is the only socket in this set
|
||||
FD_ZERO(&sockfds);
|
||||
FD_SET(_socket, &sockfds);
|
||||
|
||||
// We define a "immediate" timeout
|
||||
timeval timeout;
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 0; // Return immediately, if possible
|
||||
|
||||
// Wait for input
|
||||
// As by 2017-12-14, it seems that FD_SETSIZE is defined as 0x40, but socket IDs now
|
||||
// start at 0x1000, so we need to use _socket+1 here
|
||||
select(_socket + 1, &sockfds, NULL, NULL, &timeout);
|
||||
|
||||
// There is input
|
||||
if (FD_ISSET(_socket, &sockfds)) {
|
||||
int socketIdentifier = createConnection(freeConnectionIdx);
|
||||
|
||||
// If initializing did not work, discard the new socket immediately
|
||||
if (socketIdentifier < 0) {
|
||||
delete _connections[freeConnectionIdx];
|
||||
_connections[freeConnectionIdx] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
int HTTPServer::createConnection(int idx) {
|
||||
HTTPConnection * newConnection = new HTTPConnection(this);
|
||||
_connections[idx] = newConnection;
|
||||
return newConnection->initialize(_socket, &_defaultHeaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method prepares the tcp server socket
|
||||
*/
|
||||
uint8_t HTTPServer::setupSocket() {
|
||||
// (AF_INET = IPv4, SOCK_STREAM = TCP)
|
||||
_socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
if (_socket>=0) {
|
||||
_sock_addr.sin_family = AF_INET;
|
||||
// Listen on all interfaces
|
||||
_sock_addr.sin_addr.s_addr = _bindAddress;
|
||||
// Set the server port
|
||||
_sock_addr.sin_port = htons(_port);
|
||||
|
||||
// Now bind the TCP socket we did create above to the socket address we specified
|
||||
// (The TCP-socket now listens on 0.0.0.0:port)
|
||||
int err = bind(_socket, (struct sockaddr* )&_sock_addr, sizeof(_sock_addr));
|
||||
if(!err) {
|
||||
err = listen(_socket, _maxConnections);
|
||||
if (!err) {
|
||||
return 1;
|
||||
} else {
|
||||
close(_socket);
|
||||
_socket = -1;
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
close(_socket);
|
||||
_socket = -1;
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
_socket = -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void HTTPServer::teardownSocket() {
|
||||
// Close the actual server sockets
|
||||
close(_socket);
|
||||
_socket = -1;
|
||||
}
|
||||
|
||||
} /* namespace httpsserver */
|
76
src/http/HTTPServer.hpp
Normal file
76
src/http/HTTPServer.hpp
Normal file
@ -0,0 +1,76 @@
|
||||
#ifndef SRC_HTTPSERVER_HPP_
|
||||
#define SRC_HTTPSERVER_HPP_
|
||||
|
||||
// Standard library
|
||||
#include <string>
|
||||
|
||||
// Arduino stuff
|
||||
#include <Arduino.h>
|
||||
|
||||
// Required for sockets
|
||||
#include "lwip/netdb.h"
|
||||
#undef read
|
||||
#include "lwip/sockets.h"
|
||||
#include "lwip/inet.h"
|
||||
|
||||
// Internal includes
|
||||
#include "HTTPSServerConstants.hpp"
|
||||
#include "HTTPHeaders.hpp"
|
||||
#include "HTTPHeader.hpp"
|
||||
#include "ResourceNode.hpp"
|
||||
#include "ResourceResolver.hpp"
|
||||
#include "ResolvedResource.hpp"
|
||||
#include "HTTPConnection.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
/**
|
||||
* \brief Main implementation for the plain HTTP server. Use HTTPSServer for TLS support
|
||||
*/
|
||||
class HTTPServer : public ResourceResolver {
|
||||
public:
|
||||
HTTPServer(const uint16_t portHTTPS = 80, const uint8_t maxConnections = 8, const in_addr_t bindAddress = 0);
|
||||
virtual ~HTTPServer();
|
||||
|
||||
uint8_t start();
|
||||
void stop();
|
||||
bool isRunning();
|
||||
|
||||
void loop();
|
||||
|
||||
void setDefaultHeader(std::string name, std::string value);
|
||||
|
||||
protected:
|
||||
// Static configuration. Port, keys, etc. ====================
|
||||
// Certificate that should be used (includes private key)
|
||||
const uint16_t _port;
|
||||
|
||||
// Max parallel connections that the server will accept
|
||||
const uint8_t _maxConnections;
|
||||
// Address to bind to (0 = all interfaces)
|
||||
const in_addr_t _bindAddress;
|
||||
|
||||
//// Runtime data ============================================
|
||||
// The array of connections that are currently active
|
||||
HTTPConnection ** _connections;
|
||||
// Status of the server: Are we running, or not?
|
||||
boolean _running;
|
||||
// The server socket
|
||||
int _socket;
|
||||
|
||||
// The server socket address, that our service is bound to
|
||||
sockaddr_in _sock_addr;
|
||||
// Headers that are included in every response
|
||||
HTTPHeaders _defaultHeaders;
|
||||
|
||||
// Setup functions
|
||||
virtual uint8_t setupSocket();
|
||||
virtual void teardownSocket();
|
||||
|
||||
// Helper functions
|
||||
virtual int createConnection(int idx);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* SRC_HTTPSERVER_HPP_ */
|
128
src/http/HTTPURLEncodedBodyParser.cpp
Normal file
128
src/http/HTTPURLEncodedBodyParser.cpp
Normal file
@ -0,0 +1,128 @@
|
||||
#include "HTTPURLEncodedBodyParser.hpp"
|
||||
|
||||
#define CHUNKSIZE 512
|
||||
#define MINCHUNKSIZE 64
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
HTTPURLEncodedBodyParser::HTTPURLEncodedBodyParser(HTTPRequest * req):
|
||||
HTTPBodyParser(req),
|
||||
bodyBuffer(NULL),
|
||||
bodyPtr(NULL),
|
||||
bodyLength(0),
|
||||
fieldBuffer(""),
|
||||
fieldPtr(NULL),
|
||||
fieldRemainingLength(0)
|
||||
{
|
||||
bodyLength = _request->getContentLength();
|
||||
if (bodyLength) {
|
||||
// We know the body length. We try to read that much and give an error if it fails.
|
||||
bodyBuffer = (char *)malloc(bodyLength+1);
|
||||
if (bodyBuffer == NULL) {
|
||||
HTTPS_LOGE("HTTPURLEncodedBodyParser: out of memory");
|
||||
return;
|
||||
}
|
||||
bodyPtr = bodyBuffer;
|
||||
size_t toRead = bodyLength;
|
||||
while(toRead > 0) {
|
||||
size_t didRead = _request->readChars(bodyPtr, toRead);
|
||||
if (didRead == 0) {
|
||||
HTTPS_LOGE("HTTPURLEncodedBodyParser: short read");
|
||||
bodyLength = bodyPtr - bodyBuffer;
|
||||
break;
|
||||
}
|
||||
bodyPtr += didRead;
|
||||
toRead -= didRead;
|
||||
}
|
||||
} else {
|
||||
// We don't know the length. Read as much as possible.
|
||||
bodyBuffer = (char *)malloc(CHUNKSIZE+1);
|
||||
if (bodyBuffer == NULL) {
|
||||
HTTPS_LOGE("HTTPURLEncodedBodyParser: out of memory");
|
||||
return;
|
||||
}
|
||||
bodyPtr = bodyBuffer;
|
||||
size_t bufferUsed = 0;
|
||||
size_t bufferAvailable = CHUNKSIZE;
|
||||
while(!_request->requestComplete()) {
|
||||
if (bufferAvailable < MINCHUNKSIZE) {
|
||||
char *pBuf = (char *)realloc(bodyBuffer, bufferUsed + CHUNKSIZE+1);
|
||||
if (pBuf == NULL) {
|
||||
HTTPS_LOGE("HTTPURLEncodedBodyParser: out of memory");
|
||||
free(bodyBuffer);
|
||||
bodyBuffer = NULL;
|
||||
return;
|
||||
}
|
||||
bodyBuffer = pBuf;
|
||||
bufferAvailable = CHUNKSIZE;
|
||||
}
|
||||
size_t didRead = _request->readChars(bodyBuffer+bufferUsed, bufferAvailable);
|
||||
bufferUsed += didRead;
|
||||
bufferAvailable -= didRead;
|
||||
}
|
||||
bodyLength = bufferUsed;
|
||||
}
|
||||
bodyPtr = bodyBuffer;
|
||||
bodyBuffer[bodyLength] = '\0';
|
||||
}
|
||||
|
||||
HTTPURLEncodedBodyParser::~HTTPURLEncodedBodyParser() {
|
||||
if (bodyBuffer) {
|
||||
free(bodyBuffer);
|
||||
}
|
||||
bodyBuffer = NULL;
|
||||
}
|
||||
|
||||
bool HTTPURLEncodedBodyParser::nextField() {
|
||||
fieldBuffer = "";
|
||||
fieldPtr = NULL;
|
||||
fieldRemainingLength = 0;
|
||||
|
||||
char *equalPtr = index(bodyPtr, '=');
|
||||
if (equalPtr == NULL) {
|
||||
return false;
|
||||
}
|
||||
fieldName = std::string(bodyPtr, equalPtr-bodyPtr);
|
||||
|
||||
char *valuePtr = equalPtr + 1;
|
||||
char *endPtr = index(valuePtr, '&');
|
||||
if (endPtr == NULL) {
|
||||
endPtr = equalPtr + strlen(equalPtr);
|
||||
bodyPtr = endPtr;
|
||||
} else {
|
||||
bodyPtr = endPtr+1;
|
||||
}
|
||||
fieldBuffer = std::string(valuePtr, endPtr - valuePtr);
|
||||
fieldBuffer = urlDecode(fieldBuffer);
|
||||
fieldRemainingLength = fieldBuffer.size();
|
||||
fieldPtr = fieldBuffer.c_str();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string HTTPURLEncodedBodyParser::getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
std::string HTTPURLEncodedBodyParser::getFieldFilename() {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string HTTPURLEncodedBodyParser::getFieldMimeType() {
|
||||
return std::string("text/plain");
|
||||
}
|
||||
|
||||
bool HTTPURLEncodedBodyParser::endOfField() {
|
||||
return fieldRemainingLength <= 0;
|
||||
}
|
||||
|
||||
size_t HTTPURLEncodedBodyParser::read(byte* buffer, size_t bufferSize) {
|
||||
if (bufferSize > fieldRemainingLength) {
|
||||
bufferSize = fieldRemainingLength;
|
||||
}
|
||||
memcpy(buffer, fieldPtr, bufferSize);
|
||||
fieldRemainingLength -= bufferSize;
|
||||
fieldPtr += bufferSize;
|
||||
return bufferSize;
|
||||
}
|
||||
|
||||
} /* namespace httpsserver */
|
32
src/http/HTTPURLEncodedBodyParser.hpp
Normal file
32
src/http/HTTPURLEncodedBodyParser.hpp
Normal file
@ -0,0 +1,32 @@
|
||||
#ifndef SRC_HTTPURLENCODEDBODYPARSER_HPP_
|
||||
#define SRC_HTTPURLENCODEDBODYPARSER_HPP_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "HTTPBodyParser.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
class HTTPURLEncodedBodyParser : public HTTPBodyParser {
|
||||
public:
|
||||
// From HTTPBodyParser
|
||||
HTTPURLEncodedBodyParser(HTTPRequest * req);
|
||||
~HTTPURLEncodedBodyParser();
|
||||
virtual bool nextField();
|
||||
virtual std::string getFieldName();
|
||||
virtual std::string getFieldFilename();
|
||||
virtual std::string getFieldMimeType();
|
||||
virtual bool endOfField();
|
||||
virtual size_t read(byte* buffer, size_t bufferSize);
|
||||
protected:
|
||||
char *bodyBuffer;
|
||||
char *bodyPtr;
|
||||
size_t bodyLength;
|
||||
std::string fieldName;
|
||||
std::string fieldBuffer;
|
||||
const char *fieldPtr;
|
||||
size_t fieldRemainingLength;
|
||||
};
|
||||
|
||||
} // namespace httpserver
|
||||
|
||||
#endif
|
15
src/http/HTTPValidator.cpp
Normal file
15
src/http/HTTPValidator.cpp
Normal file
@ -0,0 +1,15 @@
|
||||
#include "HTTPValidator.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
HTTPValidator::HTTPValidator(const uint8_t idx, const HTTPValidationFunction * validatorFunction):
|
||||
_idx(idx),
|
||||
_validatorFunction(validatorFunction) {
|
||||
|
||||
}
|
||||
|
||||
HTTPValidator::~HTTPValidator() {
|
||||
|
||||
}
|
||||
|
||||
} /* namespace httpsserver */
|
23
src/http/HTTPValidator.hpp
Normal file
23
src/http/HTTPValidator.hpp
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef SRC_HTTPVALIDATOR_HPP_
|
||||
#define SRC_HTTPVALIDATOR_HPP_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
typedef bool (HTTPValidationFunction)(std::string);
|
||||
|
||||
/**
|
||||
* \brief Internal representation of a validator function
|
||||
*/
|
||||
class HTTPValidator {
|
||||
public:
|
||||
HTTPValidator(const uint8_t idx, const HTTPValidationFunction * validatorFunction);
|
||||
virtual ~HTTPValidator();
|
||||
const uint8_t _idx;
|
||||
const HTTPValidationFunction * _validatorFunction;
|
||||
};
|
||||
|
||||
} /* namespace httpsserver */
|
||||
|
||||
#endif /* SRC_HTTPVALIDATOR_HPP_ */
|
40
src/http/ResolvedResource.cpp
Normal file
40
src/http/ResolvedResource.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
#include "ResolvedResource.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
ResolvedResource::ResolvedResource() {
|
||||
_matchingNode = NULL;
|
||||
_params = NULL;
|
||||
}
|
||||
|
||||
ResolvedResource::~ResolvedResource() {
|
||||
// Delete only params, nodes are reused/server-internal
|
||||
if (_params != NULL) {
|
||||
delete _params;
|
||||
}
|
||||
}
|
||||
|
||||
bool ResolvedResource::didMatch() {
|
||||
return _matchingNode != NULL;
|
||||
}
|
||||
|
||||
HTTPNode * ResolvedResource::getMatchingNode() {
|
||||
return _matchingNode;
|
||||
}
|
||||
|
||||
void ResolvedResource::setMatchingNode(HTTPNode * node) {
|
||||
_matchingNode = node;
|
||||
}
|
||||
|
||||
ResourceParameters * ResolvedResource::getParams() {
|
||||
return _params;
|
||||
}
|
||||
|
||||
void ResolvedResource::setParams(ResourceParameters * params) {
|
||||
if (_params != NULL && _params!=params) {
|
||||
delete _params;
|
||||
}
|
||||
_params = params;
|
||||
}
|
||||
|
||||
} /* namespace httpsserver */
|
30
src/http/ResolvedResource.hpp
Normal file
30
src/http/ResolvedResource.hpp
Normal file
@ -0,0 +1,30 @@
|
||||
#ifndef SRC_RESOLVEDRESOURCE_HPP_
|
||||
#define SRC_RESOLVEDRESOURCE_HPP_
|
||||
|
||||
#include "ResourceNode.hpp"
|
||||
#include "ResourceParameters.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
/**
|
||||
* \brief This class represents a resolved resource, meaning the result of mapping a string URL to an HTTPNode
|
||||
*/
|
||||
class ResolvedResource {
|
||||
public:
|
||||
ResolvedResource();
|
||||
~ResolvedResource();
|
||||
|
||||
void setMatchingNode(HTTPNode * node);
|
||||
HTTPNode * getMatchingNode();
|
||||
bool didMatch();
|
||||
ResourceParameters * getParams();
|
||||
void setParams(ResourceParameters * params);
|
||||
|
||||
private:
|
||||
HTTPNode * _matchingNode;
|
||||
ResourceParameters * _params;
|
||||
};
|
||||
|
||||
} /* namespace httpsserver */
|
||||
|
||||
#endif /* SRC_RESOLVEDRESOURCE_HPP_ */
|
16
src/http/ResourceNode.cpp
Normal file
16
src/http/ResourceNode.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
#include "ResourceNode.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
ResourceNode::ResourceNode(const std::string &path, const std::string &method, const HTTPSCallbackFunction * callback, const std::string &tag):
|
||||
HTTPNode(path, HANDLER_CALLBACK, tag),
|
||||
_method(method),
|
||||
_callback(callback) {
|
||||
|
||||
}
|
||||
|
||||
ResourceNode::~ResourceNode() {
|
||||
|
||||
}
|
||||
|
||||
} /* namespace httpsserver */
|
28
src/http/ResourceNode.hpp
Normal file
28
src/http/ResourceNode.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef SRC_RESOURCENODE_HPP_
|
||||
#define SRC_RESOURCENODE_HPP_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "HTTPNode.hpp"
|
||||
#include "HTTPSCallbackFunction.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
/**
|
||||
* \brief This HTTPNode represents a route that maps to a regular HTTP request for a resource (static or dynamic)
|
||||
*
|
||||
* It therefore contrasts to the WebsocketNode, which handles requests for Websockets.
|
||||
*/
|
||||
class ResourceNode : public HTTPNode {
|
||||
public:
|
||||
ResourceNode(const std::string &path, const std::string &method, const HTTPSCallbackFunction * callback, const std::string &tag = "");
|
||||
virtual ~ResourceNode();
|
||||
|
||||
const std::string _method;
|
||||
const HTTPSCallbackFunction * _callback;
|
||||
std::string getMethod() { return _method; }
|
||||
};
|
||||
|
||||
} /* namespace httpsserver */
|
||||
|
||||
#endif /* SRC_RESOURCENODE_HPP_ */
|
167
src/http/ResourceParameters.cpp
Normal file
167
src/http/ResourceParameters.cpp
Normal file
@ -0,0 +1,167 @@
|
||||
#include "ResourceParameters.hpp"
|
||||
#include "HTTPSServerConstants.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
ResourceParameters::ResourceParameters() {
|
||||
|
||||
}
|
||||
|
||||
ResourceParameters::~ResourceParameters() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether a specific HTTPS query parameter is set.
|
||||
*
|
||||
* Query parameters are key-value pairs that are appended to the URI after a question mark.
|
||||
*
|
||||
* If the key exists (either as a value-less parameter or with a value), the function returns true.
|
||||
*
|
||||
* @param name The parameter to check
|
||||
* @return true iff the parameter exists
|
||||
*/
|
||||
bool ResourceParameters::isQueryParameterSet(std::string const &name) {
|
||||
for(auto queryParam = _queryParams.begin(); queryParam != _queryParams.end(); ++queryParam) {
|
||||
if ((*queryParam).first.compare(name)==0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an HTTP query parameter.
|
||||
*
|
||||
* Query parameters are key-value pairs that are appended to the URI after a question mark.
|
||||
*
|
||||
* The name parameter specifies the name of the query parameter to retrieve. If it is set,
|
||||
* the value is written to the value parameter and true is returned. If the parameter does
|
||||
* not exist, value is left unchanged and false is returned. If the parameter is used
|
||||
* without a value, an empty string is written to value and true is returned.
|
||||
*
|
||||
* @param name The name of the parameter to retrieve. If the parameter exists multiple times,
|
||||
* the first occurence is used for the value. Use beginQueryParameters() to retrieve all values.
|
||||
* @param value The target to write the value to, if the parameter exists.
|
||||
* @return true iff the parameter exists and the corresponding value has been written.
|
||||
*/
|
||||
bool ResourceParameters::getQueryParameter(std::string const &name, std::string &value) {
|
||||
for(auto queryParam = _queryParams.begin(); queryParam != _queryParams.end(); ++queryParam) {
|
||||
if ((*queryParam).first.compare(name)==0) {
|
||||
value=(*queryParam).second;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of query parameters.
|
||||
*
|
||||
* Query parameters are key-value pairs that are appended to the URI after a question mark.
|
||||
*
|
||||
* @param unique If true, return the number of unique keys (using the same key multiple times
|
||||
* is counted only once). False by default, as checking for uniqueness is not efficient.
|
||||
* @return Number of query parameters
|
||||
*/
|
||||
size_t ResourceParameters::getQueryParameterCount(bool unique) {
|
||||
if (!unique) {
|
||||
return _queryParams.size();
|
||||
}
|
||||
size_t count = 0;
|
||||
for(auto a = _queryParams.begin(); a != _queryParams.end(); ++a) {
|
||||
bool exists = false;
|
||||
for(auto b = _queryParams.begin(); !exists && b != a; ++b) {
|
||||
exists = (*a).first.compare((*b).first)==0;
|
||||
}
|
||||
count += exists ? 0 : 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Provides iterator access to the query parameters
|
||||
*
|
||||
* Query parameters are key-value pairs that are appended to the URI after a question mark.
|
||||
*
|
||||
* If you want just a specific parameter, have a look at getQueryParameter()
|
||||
*
|
||||
* The iterator will yield pairs of std::string, of which the first value specifies the
|
||||
* query parameter key and the second value corresponds to the query parameters value.
|
||||
* If the entry is value-less, the second value will be the empty string.
|
||||
*
|
||||
* If the same key is used multiple times in the query, the iterator will yield it multiple
|
||||
* times, once for each occurence with the specific value.
|
||||
*
|
||||
* @return Iterator over std::pairs of std::strings that represent (key, value) pairs
|
||||
*/
|
||||
std::vector<std::pair<std::string,std::string>>::iterator ResourceParameters::beginQueryParameters() {
|
||||
return _queryParams.begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Counterpart to beginQueryParameters() for iterating over query parameters
|
||||
*/
|
||||
std::vector<std::pair<std::string,std::string>>::iterator ResourceParameters::endQueryParameters() {
|
||||
return _queryParams.end();
|
||||
}
|
||||
|
||||
void ResourceParameters::setQueryParameter(std::string const &name, std::string const &value) {
|
||||
std::pair<std::string, std::string> param;
|
||||
param.first = name;
|
||||
param.second = value;
|
||||
_queryParams.push_back(param);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks for the existence of a path parameter and returns it as string.
|
||||
*
|
||||
* Path parameters are defined by an asterisk as placeholder when specifying the path of
|
||||
* the ResourceNode and addressed by an index starting at 0 for the first parameter.
|
||||
*
|
||||
* For values of idx that have no matching placeholder, value is left unchanged and the
|
||||
* method will return false.
|
||||
*
|
||||
* @param idx Defines the index of the parameter to return, starting with 0.
|
||||
* @param value The value is written into this parameter.
|
||||
* @return true iff the value could be written.
|
||||
*/
|
||||
bool ResourceParameters::getPathParameter(size_t const idx, std::string &value) {
|
||||
if (idx < _pathParams.size()) {
|
||||
value = _pathParams.at(idx);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Directly returns a path parameter
|
||||
*
|
||||
* Path parameters are defined by an asterisk as placeholder when specifying the path of
|
||||
* the ResourceNode and addressed by an index starting at 0 for the first parameter.
|
||||
*
|
||||
* This method will return the parameter specified by the index. The caller is responsible
|
||||
* to assure that the index exists. Otherwise, an empty string will be returned.
|
||||
*
|
||||
* @param idx Defines the index of the parameter to return, starting with 0.
|
||||
* @return the value of the placeholder
|
||||
*/
|
||||
std::string ResourceParameters::getPathParameter(size_t const idx) {
|
||||
if (idx < _pathParams.size()) {
|
||||
return _pathParams.at(idx);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void ResourceParameters::resetPathParameters() {
|
||||
_pathParams.clear();
|
||||
}
|
||||
|
||||
void ResourceParameters::setPathParameter(size_t idx, std::string const &val) {
|
||||
if(idx>=_pathParams.size()) {
|
||||
_pathParams.resize(idx + 1);
|
||||
}
|
||||
_pathParams.at(idx) = val;
|
||||
}
|
||||
|
||||
} /* namespace httpsserver */
|
59
src/http/ResourceParameters.hpp
Normal file
59
src/http/ResourceParameters.hpp
Normal file
@ -0,0 +1,59 @@
|
||||
#ifndef SRC_RESOURCEPARAMETERS_HPP_
|
||||
#define SRC_RESOURCEPARAMETERS_HPP_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <string>
|
||||
// Arduino declares it's own min max, incompatible with the stl...
|
||||
#undef min
|
||||
#undef max
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
#include "util.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
class ResourceResolver;
|
||||
|
||||
/**
|
||||
* @brief The ResourceParameters provide access to the parameters passed in the URI.
|
||||
*
|
||||
* There are two types of parameters: Path parameters and query parameters.
|
||||
*
|
||||
* Path parameters are the values that fill the asterisk placeholders in the route
|
||||
* definition of a ResourceNode.
|
||||
*
|
||||
* Query parameters are the key-value pairs after a question mark which can be added
|
||||
* to each request, either by specifying them manually or as result of submitting an
|
||||
* HTML form with a GET as method property.
|
||||
*/
|
||||
class ResourceParameters {
|
||||
public:
|
||||
ResourceParameters();
|
||||
virtual ~ResourceParameters();
|
||||
|
||||
bool isQueryParameterSet(std::string const &name);
|
||||
bool getQueryParameter(std::string const &name, std::string &value);
|
||||
std::vector<std::pair<std::string,std::string>>::iterator beginQueryParameters();
|
||||
std::vector<std::pair<std::string,std::string>>::iterator endQueryParameters();
|
||||
size_t getQueryParameterCount(bool unique=false);
|
||||
bool getPathParameter(size_t const idx, std::string &value);
|
||||
std::string getPathParameter(size_t const idx);
|
||||
|
||||
protected:
|
||||
friend class ResourceResolver;
|
||||
void setQueryParameter(std::string const &name, std::string const &value);
|
||||
void resetPathParameters();
|
||||
void setPathParameter(size_t idx, std::string const &val);
|
||||
|
||||
private:
|
||||
/** Parameters in the path of the URL, the actual values for asterisk placeholders */
|
||||
std::vector<std::string> _pathParams;
|
||||
/** HTTP Query parameters, as key-value pairs */
|
||||
std::vector<std::pair<std::string, std::string>> _queryParams;
|
||||
};
|
||||
|
||||
} /* namespace httpsserver */
|
||||
|
||||
#endif /* SRC_RESOURCEPARAMETERS_HPP_ */
|
179
src/http/ResourceResolver.cpp
Normal file
179
src/http/ResourceResolver.cpp
Normal file
@ -0,0 +1,179 @@
|
||||
#include "ResourceResolver.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
ResourceResolver::ResourceResolver() {
|
||||
_nodes = new std::vector<HTTPNode *>();
|
||||
_defaultNode = NULL;
|
||||
}
|
||||
|
||||
ResourceResolver::~ResourceResolver() {
|
||||
delete _nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will register the HTTPSNode so it is reachable and its callback gets called for a request
|
||||
*/
|
||||
void ResourceResolver::registerNode(HTTPNode *node) {
|
||||
_nodes->push_back(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be used to deactivate a HTTPSNode that has been registered previously
|
||||
*/
|
||||
void ResourceResolver::unregisterNode(HTTPNode *node) {
|
||||
|
||||
}
|
||||
|
||||
void ResourceResolver::resolveNode(const std::string &method, const std::string &url, ResolvedResource &resolvedResource, HTTPNodeType nodeType) {
|
||||
// Reset the resource
|
||||
resolvedResource.setMatchingNode(NULL);
|
||||
resolvedResource.setParams(NULL);
|
||||
|
||||
// Memory management of this object will be performed by the ResolvedResource instance
|
||||
ResourceParameters * params = new ResourceParameters();
|
||||
|
||||
// Split URL in resource name and request params. Request params start after an optional '?'
|
||||
size_t reqparamIdx = url.find('?');
|
||||
// Store this index to stop path parsing there
|
||||
size_t pathEnd = reqparamIdx != std::string::npos ? reqparamIdx : url.size();
|
||||
|
||||
// If no '?' is contained in url, 0:npos will return the string as it is
|
||||
std::string resourceName = url.substr(0, reqparamIdx);
|
||||
|
||||
// Set request params in params object if a '?' exists
|
||||
if (reqparamIdx != std::string::npos) {
|
||||
do {
|
||||
// Drop the '?' or '&'
|
||||
reqparamIdx += 1;
|
||||
|
||||
// Parameters are separated by '&'
|
||||
size_t nextparamIdx = url.find('&', reqparamIdx);
|
||||
|
||||
// Get the "name=value" string
|
||||
std::string param = nextparamIdx == std::string::npos ?
|
||||
url.substr(reqparamIdx) :
|
||||
url.substr(reqparamIdx, nextparamIdx - reqparamIdx);
|
||||
|
||||
if (param.length() > 0) {
|
||||
// Find the position where the string has to be split
|
||||
size_t nvSplitIdx = param.find('=');
|
||||
|
||||
// Use empty string if only name is set. /foo?bar&baz=1 will return "" for bar
|
||||
std::string name = urlDecode(param.substr(0, nvSplitIdx));
|
||||
std::string value = "";
|
||||
if (nvSplitIdx != std::string::npos) {
|
||||
value = urlDecode(param.substr(nvSplitIdx+1));
|
||||
}
|
||||
|
||||
// Now we finally have name and value.
|
||||
params->setQueryParameter(name, value);
|
||||
}
|
||||
|
||||
// Update reqparamIdx
|
||||
reqparamIdx = nextparamIdx;
|
||||
|
||||
} while(reqparamIdx != std::string::npos);
|
||||
}
|
||||
|
||||
|
||||
// Check whether a resource matches
|
||||
|
||||
for(std::vector<HTTPNode*>::iterator itNode = _nodes->begin(); itNode != _nodes->end(); ++itNode) {
|
||||
params->resetPathParameters();
|
||||
HTTPNode *node = *itNode;
|
||||
if (node->_nodeType==nodeType) {
|
||||
if (
|
||||
// For handler functions, check the method declared with the node
|
||||
(node->_nodeType==HANDLER_CALLBACK && ((ResourceNode*)node)->_method == method) ||
|
||||
// For websockets, the specification says that GET is the only choice
|
||||
(node->_nodeType==WEBSOCKET && method=="GET")
|
||||
) {
|
||||
HTTPS_LOGD("Testing route %s", node->_path.c_str());
|
||||
bool match = true;
|
||||
size_t paramCount = node->getPathParamCount();
|
||||
// indices in input and pattern
|
||||
size_t inputIdx = 0, pathIdx = 0;
|
||||
HTTPS_LOGD("(INIT) inputIdx: %d, pathIdx: %d, pathEnd: %d, path: %s, url: %s",
|
||||
inputIdx, pathIdx, pathEnd, node->_path.c_str(), url.c_str());
|
||||
|
||||
for (size_t paramIdx = 0; match && paramIdx < paramCount; paramIdx += 1) {
|
||||
HTTPS_LOGD("(LOOP) inputIdx: %d, pathIdx: %d, pathEnd: %d, path: %s, url: %s",
|
||||
inputIdx, pathIdx, pathEnd, node->_path.c_str(), url.c_str());
|
||||
// Test static path before the parameter
|
||||
size_t paramPos = node->getParamIdx(paramIdx);
|
||||
size_t staticLength = paramPos - pathIdx;
|
||||
match &= url.substr(inputIdx, staticLength) == node->_path.substr(pathIdx, staticLength);
|
||||
inputIdx += staticLength;
|
||||
pathIdx += staticLength;
|
||||
|
||||
// Extract parameter value
|
||||
if (match) {
|
||||
size_t paramEnd = url.find('/', inputIdx);
|
||||
if (paramEnd == std::string::npos && inputIdx <= pathEnd) {
|
||||
// Consume the remaining input (might be "" for the last param)
|
||||
paramEnd = pathEnd;
|
||||
}
|
||||
if (paramEnd != std::string::npos) {
|
||||
size_t paramLength = paramEnd - inputIdx;
|
||||
params->setPathParameter(paramIdx, urlDecode(url.substr(inputIdx, paramLength)));
|
||||
pathIdx += 1;
|
||||
inputIdx += paramLength;
|
||||
} else {
|
||||
match = false;
|
||||
HTTPS_LOGD("(LOOP) No match on param part");
|
||||
}
|
||||
} else {
|
||||
HTTPS_LOGD("(LOOP) No match on static part");
|
||||
}
|
||||
}
|
||||
HTTPS_LOGD("(STTC) inputIdx: %d, pathIdx: %d, pathEnd: %d, path: %s, url: %s",
|
||||
inputIdx, pathIdx, pathEnd, node->_path.c_str(), url.c_str());
|
||||
// Test static path after the parameter (up to pathEnd)
|
||||
if (match) {
|
||||
match = url.substr(inputIdx, pathEnd - inputIdx)==node->_path.substr(pathIdx);
|
||||
}
|
||||
HTTPS_LOGD("(END ) inputIdx: %d, pathIdx: %d, pathEnd: %d, path: %s, url: %s",
|
||||
inputIdx, pathIdx, pathEnd, node->_path.c_str(), url.c_str());
|
||||
|
||||
if (match) {
|
||||
resolvedResource.setMatchingNode(node);
|
||||
HTTPS_LOGD("It's a match!");
|
||||
break;
|
||||
}
|
||||
} // method check
|
||||
} // node type check
|
||||
} // resource node for loop
|
||||
|
||||
// If the resource did not match, configure the default resource
|
||||
if (!resolvedResource.didMatch() && _defaultNode != NULL) {
|
||||
params->resetPathParameters();
|
||||
resolvedResource.setMatchingNode(_defaultNode);
|
||||
}
|
||||
|
||||
// If resolving did work, set the params, otherwise delete them
|
||||
if (resolvedResource.didMatch()) {
|
||||
// The resolvedResource now takes care of memory management for the params
|
||||
resolvedResource.setParams(params);
|
||||
} else {
|
||||
delete params;
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceResolver::addMiddleware(const HTTPSMiddlewareFunction * mwFunction) {
|
||||
_middleware.push_back(mwFunction);
|
||||
}
|
||||
|
||||
void ResourceResolver::removeMiddleware(const HTTPSMiddlewareFunction * mwFunction) {
|
||||
_middleware.erase(std::remove(_middleware.begin(), _middleware.end(), mwFunction), _middleware.end());
|
||||
}
|
||||
|
||||
const std::vector<HTTPSMiddlewareFunction*> ResourceResolver::getMiddleware() {
|
||||
return _middleware;
|
||||
}
|
||||
|
||||
void ResourceResolver::setDefaultNode(HTTPNode * defaultNode) {
|
||||
_defaultNode = defaultNode;
|
||||
}
|
||||
|
||||
}
|
51
src/http/ResourceResolver.hpp
Normal file
51
src/http/ResourceResolver.hpp
Normal file
@ -0,0 +1,51 @@
|
||||
#ifndef SRC_RESOURCERESOLVER_HPP_
|
||||
#define SRC_RESOURCERESOLVER_HPP_
|
||||
|
||||
#include <string>
|
||||
// Arduino declares it's own min max, incompatible with the stl...
|
||||
#undef min
|
||||
#undef max
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include "HTTPNode.hpp"
|
||||
#include "WebsocketNode.hpp"
|
||||
#include "ResourceNode.hpp"
|
||||
#include "ResolvedResource.hpp"
|
||||
#include "HTTPMiddlewareFunction.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
/**
|
||||
* \brief This class is used internally to resolve a string URL to the corresponding HTTPNode
|
||||
*/
|
||||
class ResourceResolver {
|
||||
public:
|
||||
ResourceResolver();
|
||||
~ResourceResolver();
|
||||
|
||||
void registerNode(HTTPNode *node);
|
||||
void unregisterNode(HTTPNode *node);
|
||||
void setDefaultNode(HTTPNode *node);
|
||||
void resolveNode(const std::string &method, const std::string &url, ResolvedResource &resolvedResource, HTTPNodeType nodeType);
|
||||
|
||||
/** Add a middleware function to the end of the middleware function chain. See HTTPSMiddlewareFunction.hpp for details. */
|
||||
void addMiddleware(const HTTPSMiddlewareFunction * mwFunction);
|
||||
/** Remove a specific function from the middleware function chain. */
|
||||
void removeMiddleware(const HTTPSMiddlewareFunction * mwFunction);
|
||||
/** Get the current middleware chain with a resource function at the end */
|
||||
const std::vector<HTTPSMiddlewareFunction*> getMiddleware();
|
||||
|
||||
private:
|
||||
|
||||
// This vector holds all nodes (with callbacks) that are registered
|
||||
std::vector<HTTPNode*> * _nodes;
|
||||
HTTPNode * _defaultNode;
|
||||
|
||||
// Middleware functions, if any are registered. Will be called in order of the vector.
|
||||
std::vector<const HTTPSMiddlewareFunction*> _middleware;
|
||||
};
|
||||
|
||||
} /* namespace httpsserver */
|
||||
|
||||
#endif /* SRC_RESOURCERESOLVER_HPP_ */
|
310
src/http/SSLCert.cpp
Normal file
310
src/http/SSLCert.cpp
Normal file
@ -0,0 +1,310 @@
|
||||
#include "SSLCert.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
SSLCert::SSLCert(unsigned char * certData, uint16_t certLength, unsigned char * pkData, uint16_t pkLength):
|
||||
_certLength(certLength),
|
||||
_certData(certData),
|
||||
_pkLength(pkLength),
|
||||
_pkData(pkData) {
|
||||
|
||||
}
|
||||
|
||||
SSLCert::~SSLCert() {
|
||||
// TODO Auto-generated destructor stub
|
||||
}
|
||||
|
||||
|
||||
uint16_t SSLCert::getCertLength() {
|
||||
return _certLength;
|
||||
}
|
||||
|
||||
uint16_t SSLCert::getPKLength() {
|
||||
return _pkLength;
|
||||
}
|
||||
|
||||
unsigned char * SSLCert::getCertData() {
|
||||
return _certData;
|
||||
}
|
||||
|
||||
unsigned char * SSLCert::getPKData() {
|
||||
return _pkData;
|
||||
}
|
||||
|
||||
void SSLCert::setPK(unsigned char * pkData, uint16_t length) {
|
||||
_pkData = pkData;
|
||||
_pkLength = length;
|
||||
}
|
||||
|
||||
void SSLCert::setCert(unsigned char * certData, uint16_t length) {
|
||||
_certData = certData;
|
||||
_certLength = length;
|
||||
}
|
||||
|
||||
void SSLCert::clear() {
|
||||
for(uint16_t i = 0; i < _certLength; i++) _certData[i]=0;
|
||||
delete _certData;
|
||||
_certLength = 0;
|
||||
|
||||
for(uint16_t i = 0; i < _pkLength; i++) _pkData[i] = 0;
|
||||
delete _pkData;
|
||||
_pkLength = 0;
|
||||
}
|
||||
|
||||
#ifndef HTTPS_DISABLE_SELFSIGNING
|
||||
|
||||
/**
|
||||
* Function to create the key for a self-signed certificate.
|
||||
*
|
||||
* Writes private key as DER in certCtx
|
||||
*
|
||||
* Based on programs/pkey/gen_key.c
|
||||
*/
|
||||
static int gen_key(SSLCert &certCtx, SSLKeySize keySize) {
|
||||
|
||||
// Initialize the entropy source
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_entropy_init( &entropy );
|
||||
|
||||
// Initialize the RNG
|
||||
mbedtls_ctr_drbg_context ctr_drbg;
|
||||
mbedtls_ctr_drbg_init( &ctr_drbg );
|
||||
int rngRes = mbedtls_ctr_drbg_seed(
|
||||
&ctr_drbg, mbedtls_entropy_func, &entropy,
|
||||
NULL, 0
|
||||
);
|
||||
if (rngRes != 0) {
|
||||
mbedtls_entropy_free( &entropy );
|
||||
return HTTPS_SERVER_ERROR_KEYGEN_RNG;
|
||||
}
|
||||
|
||||
// Initialize the private key
|
||||
mbedtls_pk_context key;
|
||||
mbedtls_pk_init( &key );
|
||||
int resPkSetup = mbedtls_pk_setup( &key, mbedtls_pk_info_from_type( MBEDTLS_PK_RSA ) );
|
||||
if ( resPkSetup != 0) {
|
||||
mbedtls_ctr_drbg_free( &ctr_drbg );
|
||||
mbedtls_entropy_free( &entropy );
|
||||
return HTTPS_SERVER_ERROR_KEYGEN_SETUP_PK;
|
||||
}
|
||||
|
||||
// Actual key generation
|
||||
int resPkGen = mbedtls_rsa_gen_key(
|
||||
mbedtls_pk_rsa( key ),
|
||||
mbedtls_ctr_drbg_random,
|
||||
&ctr_drbg,
|
||||
keySize,
|
||||
65537
|
||||
);
|
||||
if ( resPkGen != 0) {
|
||||
mbedtls_pk_free( &key );
|
||||
mbedtls_ctr_drbg_free( &ctr_drbg );
|
||||
mbedtls_entropy_free( &entropy );
|
||||
return HTTPS_SERVER_ERROR_KEYGEN_GEN_PK;
|
||||
}
|
||||
|
||||
// Free the entropy source and the RNG as they are no longer needed
|
||||
mbedtls_ctr_drbg_free( &ctr_drbg );
|
||||
mbedtls_entropy_free( &entropy );
|
||||
|
||||
// Allocate the space on the heap, as stack size is quite limited
|
||||
unsigned char * output_buf = new unsigned char[4096];
|
||||
if (output_buf == NULL) {
|
||||
mbedtls_pk_free( &key );
|
||||
return HTTPS_SERVER_ERROR_KEY_OUT_OF_MEM;
|
||||
}
|
||||
memset(output_buf, 0, 4096);
|
||||
|
||||
// Write the key to the temporary buffer and determine its length
|
||||
int resPkWrite = mbedtls_pk_write_key_der( &key, output_buf, 4096 );
|
||||
if (resPkWrite < 0) {
|
||||
delete[] output_buf;
|
||||
mbedtls_pk_free( &key );
|
||||
return HTTPS_SERVER_ERROR_KEY_WRITE_PK;
|
||||
}
|
||||
size_t pkLength = resPkWrite;
|
||||
unsigned char *pkOffset = output_buf + sizeof(unsigned char) * 4096 - pkLength;
|
||||
|
||||
// Copy the key into a new, fitting space on the heap
|
||||
unsigned char * output_pk = new unsigned char[pkLength];
|
||||
if (output_pk == NULL) {
|
||||
delete[] output_buf;
|
||||
mbedtls_pk_free( &key );
|
||||
return HTTPS_SERVER_ERROR_KEY_WRITE_PK;
|
||||
}
|
||||
memcpy(output_pk, pkOffset, pkLength);
|
||||
|
||||
// Clean up the temporary buffer and clear the key context
|
||||
delete[] output_buf;
|
||||
mbedtls_pk_free( &key );
|
||||
|
||||
// Set the private key in the context
|
||||
certCtx.setPK(output_pk, pkLength);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to generate the X509 certificate data for a private key
|
||||
*
|
||||
* Writes certificate in certCtx
|
||||
*
|
||||
* Based on programs/x509/cert_write.c
|
||||
*/
|
||||
static int cert_write(SSLCert &certCtx, std::string dn, std::string validityFrom, std::string validityTo) {
|
||||
int funcRes = 0;
|
||||
int stepRes = 0;
|
||||
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_ctr_drbg_context ctr_drbg;
|
||||
mbedtls_pk_context key;
|
||||
mbedtls_x509write_cert crt;
|
||||
mbedtls_mpi serial;
|
||||
unsigned char * primary_buffer;
|
||||
unsigned char *certOffset;
|
||||
unsigned char * output_buffer;
|
||||
size_t certLength;
|
||||
|
||||
// Make a C-friendly version of the distinguished name
|
||||
char dn_cstr[dn.length()+1];
|
||||
strcpy(dn_cstr, dn.c_str());
|
||||
|
||||
// Initialize the entropy source
|
||||
mbedtls_entropy_init( &entropy );
|
||||
|
||||
// Initialize the RNG
|
||||
mbedtls_ctr_drbg_init( &ctr_drbg );
|
||||
stepRes = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0 );
|
||||
if (stepRes != 0) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_RNG;
|
||||
goto error_after_entropy;
|
||||
}
|
||||
|
||||
mbedtls_pk_init( &key );
|
||||
stepRes = mbedtls_pk_parse_key( &key, certCtx.getPKData(), certCtx.getPKLength(), NULL, 0 );
|
||||
if (stepRes != 0) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_READKEY;
|
||||
goto error_after_key;
|
||||
}
|
||||
|
||||
// Start configuring the certificate
|
||||
mbedtls_x509write_crt_init( &crt );
|
||||
// Set version and hash algorithm
|
||||
mbedtls_x509write_crt_set_version( &crt, MBEDTLS_X509_CRT_VERSION_3 );
|
||||
mbedtls_x509write_crt_set_md_alg( &crt, MBEDTLS_MD_SHA256 );
|
||||
|
||||
// Set the keys (same key as we self-sign)
|
||||
mbedtls_x509write_crt_set_subject_key( &crt, &key );
|
||||
mbedtls_x509write_crt_set_issuer_key( &crt, &key );
|
||||
|
||||
// Set issuer and subject (same, as we self-sign)
|
||||
stepRes = mbedtls_x509write_crt_set_subject_name( &crt, dn_cstr );
|
||||
if (stepRes != 0) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_NAME;
|
||||
goto error_after_cert;
|
||||
}
|
||||
stepRes = mbedtls_x509write_crt_set_issuer_name( &crt, dn_cstr );
|
||||
if (stepRes != 0) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_NAME;
|
||||
goto error_after_cert;
|
||||
}
|
||||
|
||||
// Set the validity of the certificate. At the moment, it's fixed from 2019 to end of 2029.
|
||||
stepRes = mbedtls_x509write_crt_set_validity( &crt, validityFrom.c_str(), validityTo.c_str());
|
||||
if (stepRes != 0) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_VALIDITY;
|
||||
goto error_after_cert;
|
||||
}
|
||||
|
||||
// Make this a CA certificate
|
||||
stepRes = mbedtls_x509write_crt_set_basic_constraints( &crt, 1, 0 );
|
||||
if (stepRes != 0) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_VALIDITY;
|
||||
goto error_after_cert;
|
||||
}
|
||||
|
||||
// Initialize the serial number
|
||||
mbedtls_mpi_init( &serial );
|
||||
stepRes = mbedtls_mpi_read_string( &serial, 10, "1" );
|
||||
if (stepRes != 0) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_SERIAL;
|
||||
goto error_after_cert_serial;
|
||||
}
|
||||
stepRes = mbedtls_x509write_crt_set_serial( &crt, &serial );
|
||||
if (stepRes != 0) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_SERIAL;
|
||||
goto error_after_cert_serial;
|
||||
}
|
||||
|
||||
// Create buffer to write the certificate
|
||||
primary_buffer = new unsigned char[4096];
|
||||
if (primary_buffer == NULL) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_OUT_OF_MEM;
|
||||
goto error_after_cert_serial;
|
||||
}
|
||||
|
||||
// Write the actual certificate
|
||||
stepRes = mbedtls_x509write_crt_der(&crt, primary_buffer, 4096, mbedtls_ctr_drbg_random, &ctr_drbg );
|
||||
if (stepRes < 0) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_WRITE;
|
||||
goto error_after_primary_buffer;
|
||||
}
|
||||
|
||||
// Create a matching buffer
|
||||
certLength = stepRes;
|
||||
certOffset = primary_buffer + sizeof(unsigned char) * 4096 - certLength;
|
||||
|
||||
// Copy the cert into a new, fitting space on the heap
|
||||
output_buffer = new unsigned char[certLength];
|
||||
if (output_buffer == NULL) {
|
||||
funcRes = HTTPS_SERVER_ERROR_CERTGEN_OUT_OF_MEM;
|
||||
goto error_after_primary_buffer;
|
||||
}
|
||||
memcpy(output_buffer, certOffset, certLength);
|
||||
|
||||
// Configure the cert in the context
|
||||
certCtx.setCert(output_buffer, certLength);
|
||||
|
||||
// Run through the cleanup process
|
||||
error_after_primary_buffer:
|
||||
delete[] primary_buffer;
|
||||
|
||||
error_after_cert_serial:
|
||||
mbedtls_mpi_free( &serial );
|
||||
|
||||
error_after_cert:
|
||||
mbedtls_x509write_crt_free( &crt );
|
||||
|
||||
error_after_key:
|
||||
mbedtls_pk_free(&key);
|
||||
|
||||
error_after_entropy:
|
||||
mbedtls_ctr_drbg_free( &ctr_drbg );
|
||||
mbedtls_entropy_free( &entropy );
|
||||
return funcRes;
|
||||
}
|
||||
|
||||
int createSelfSignedCert(SSLCert &certCtx, SSLKeySize keySize, std::string dn, std::string validFrom, std::string validUntil) {
|
||||
|
||||
// Add the private key
|
||||
int keyRes = gen_key(certCtx, keySize);
|
||||
if (keyRes != 0) {
|
||||
// Key-generation failed, return the failure code
|
||||
return keyRes;
|
||||
}
|
||||
|
||||
// Add the self-signed certificate
|
||||
int certRes = cert_write(certCtx, dn, validFrom, validUntil);
|
||||
if (certRes != 0) {
|
||||
// Cert writing failed, reset the pk and return failure code
|
||||
certCtx.setPK(NULL, 0);
|
||||
return certRes;
|
||||
}
|
||||
|
||||
// If all went well, return 0
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // !HTTPS_DISABLE_SELFSIGNING
|
||||
|
||||
} /* namespace httpsserver */
|
182
src/http/SSLCert.hpp
Normal file
182
src/http/SSLCert.hpp
Normal file
@ -0,0 +1,182 @@
|
||||
#ifndef SRC_SSLCERT_HPP_
|
||||
#define SRC_SSLCERT_HPP_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifndef HTTPS_DISABLE_SELFSIGNING
|
||||
#include <string>
|
||||
#include <mbedtls/rsa.h>
|
||||
#include <mbedtls/entropy.h>
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
#include <mbedtls/pk.h>
|
||||
#include <mbedtls/x509.h>
|
||||
#include <mbedtls/x509_crt.h>
|
||||
#include <mbedtls/x509_csr.h>
|
||||
|
||||
#define HTTPS_SERVER_ERROR_KEYGEN 0x0F
|
||||
#define HTTPS_SERVER_ERROR_KEYGEN_RNG 0x02
|
||||
#define HTTPS_SERVER_ERROR_KEYGEN_SETUP_PK 0x03
|
||||
#define HTTPS_SERVER_ERROR_KEYGEN_GEN_PK 0x04
|
||||
#define HTTPS_SERVER_ERROR_KEY_WRITE_PK 0x05
|
||||
#define HTTPS_SERVER_ERROR_KEY_OUT_OF_MEM 0x06
|
||||
#define HTTPS_SERVER_ERROR_CERTGEN 0x1F
|
||||
#define HTTPS_SERVER_ERROR_CERTGEN_RNG 0x12
|
||||
#define HTTPS_SERVER_ERROR_CERTGEN_READKEY 0x13
|
||||
#define HTTPS_SERVER_ERROR_CERTGEN_WRITE 0x15
|
||||
#define HTTPS_SERVER_ERROR_CERTGEN_OUT_OF_MEM 0x16
|
||||
#define HTTPS_SERVER_ERROR_CERTGEN_NAME 0x17
|
||||
#define HTTPS_SERVER_ERROR_CERTGEN_SERIAL 0x18
|
||||
#define HTTPS_SERVER_ERROR_CERTGEN_VALIDITY 0x19
|
||||
|
||||
#endif // !HTTPS_DISABLE_SELFSIGNING
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
/**
|
||||
* \brief Certificate and private key that can be passed to the HTTPSServer.
|
||||
*
|
||||
* **Converting PEM to DER Files**
|
||||
*
|
||||
* Certificate:
|
||||
* ```bash
|
||||
* openssl x509 -inform PEM -outform DER -in myCert.crt -out cert.der
|
||||
* ```
|
||||
*
|
||||
* Private Key:
|
||||
* ```bash
|
||||
* openssl rsa -inform PEM -outform DER -in myCert.key -out key.der
|
||||
* ```
|
||||
*
|
||||
* **Converting DER File to C Header**
|
||||
*
|
||||
* ```bash
|
||||
* echo "#ifndef KEY_H_" > ./key.h
|
||||
* echo "#define KEY_H_" >> ./key.h
|
||||
* xxd -i key.der >> ./key.h
|
||||
* echo "#endif" >> ./key.h
|
||||
* ```
|
||||
*/
|
||||
class SSLCert {
|
||||
public:
|
||||
/**
|
||||
* \brief Creates a new SSLCert.
|
||||
*
|
||||
* The certificate and key data may be NULL (default values) if the certificate is meant
|
||||
* to be passed to createSelfSignedCert().
|
||||
*
|
||||
* Otherwise, the data must reside in a memory location that is not deleted until the server
|
||||
* using the certificate is stopped.
|
||||
*
|
||||
* \param[in] certData The certificate data to use (DER format)
|
||||
* \param[in] certLength The length of the certificate data
|
||||
* \param[in] pkData The private key data to use (DER format)
|
||||
* \param[in] pkLength The length of the private key
|
||||
*/
|
||||
SSLCert(
|
||||
unsigned char * certData = NULL,
|
||||
uint16_t certLength = 0,
|
||||
unsigned char * pkData = NULL,
|
||||
uint16_t pkLength = 0
|
||||
);
|
||||
virtual ~SSLCert();
|
||||
|
||||
/**
|
||||
* \brief Returns the length of the certificate in byte
|
||||
*/
|
||||
uint16_t getCertLength();
|
||||
|
||||
/**
|
||||
* \brief Returns the length of the private key in byte
|
||||
*/
|
||||
uint16_t getPKLength();
|
||||
|
||||
/**
|
||||
* \brief Returns the certificate data
|
||||
*/
|
||||
unsigned char * getCertData();
|
||||
|
||||
/**
|
||||
* \brief Returns the private key data
|
||||
*/
|
||||
unsigned char * getPKData();
|
||||
|
||||
/**
|
||||
* \brief Sets the private key in DER format
|
||||
*
|
||||
* The data has to reside in a place in memory that is not deleted as long as the
|
||||
* server is running.
|
||||
*
|
||||
* See SSLCert() for some information on how to generate DER data.
|
||||
*
|
||||
* \param[in] _pkData The data of the private key
|
||||
* \param[in] length The length of the private key
|
||||
*/
|
||||
void setPK(unsigned char * _pkData, uint16_t length);
|
||||
|
||||
/**
|
||||
* \brief Sets the certificate data in DER format
|
||||
*
|
||||
* The data has to reside in a place in memory that is not deleted as long as the
|
||||
* server is running.
|
||||
*
|
||||
* See SSLCert for some information on how to generate DER data.
|
||||
*
|
||||
* \param[in] _certData The data of the certificate
|
||||
* \param[in] length The length of the certificate
|
||||
*/
|
||||
void setCert(unsigned char * _certData, uint16_t length);
|
||||
|
||||
/**
|
||||
* \brief Clears the key buffers and deletes them.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
private:
|
||||
uint16_t _certLength;
|
||||
unsigned char * _certData;
|
||||
uint16_t _pkLength;
|
||||
unsigned char * _pkData;
|
||||
|
||||
};
|
||||
|
||||
#ifndef HTTPS_DISABLE_SELFSIGNING
|
||||
|
||||
/**
|
||||
* \brief Defines the key size for key generation
|
||||
*
|
||||
* Not available if the `HTTPS_DISABLE_SELFSIGNING` compiler flag is set
|
||||
*/
|
||||
enum SSLKeySize {
|
||||
/** \brief RSA key with 1024 bit */
|
||||
KEYSIZE_1024 = 1024,
|
||||
/** \brief RSA key with 2048 bit */
|
||||
KEYSIZE_2048 = 2048,
|
||||
/** \brief RSA key with 4096 bit */
|
||||
KEYSIZE_4096 = 4096
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Creates a self-signed certificate on the ESP32
|
||||
*
|
||||
* This function creates a new self-signed certificate for the given hostname on the heap.
|
||||
* Make sure to clear() it before you delete it.
|
||||
*
|
||||
* The distinguished name (dn) parameter has to follow the x509 specifications. An example
|
||||
* would be:
|
||||
* CN=myesp.local,O=acme,C=US
|
||||
*
|
||||
* The strings validFrom and validUntil have to be formatted like this:
|
||||
* "20190101000000", "20300101000000"
|
||||
*
|
||||
* This will take some time, so you should probably write the certificate data to non-volatile
|
||||
* storage when you are done.
|
||||
*
|
||||
* Setting the `HTTPS_DISABLE_SELFSIGNING` compiler flag will remove this function from the library
|
||||
*/
|
||||
int createSelfSignedCert(SSLCert &certCtx, SSLKeySize keySize, std::string dn, std::string validFrom = "20190101000000", std::string validUntil = "20300101000000");
|
||||
|
||||
#endif // !HTTPS_DISABLE_SELFSIGNING
|
||||
|
||||
} /* namespace httpsserver */
|
||||
|
||||
#endif /* SRC_SSLCERT_HPP_ */
|
14
src/http/ValidatorFunctions.cpp
Normal file
14
src/http/ValidatorFunctions.cpp
Normal file
@ -0,0 +1,14 @@
|
||||
#include "ValidatorFunctions.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
bool validateNotEmpty(std::string s) {
|
||||
return s!="";
|
||||
}
|
||||
|
||||
bool validateUnsignedInteger(std::string s) {
|
||||
for(size_t x = 0; x < s.size(); x++) {
|
||||
if (s[x]<'0' || s[x]>'9') return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
37
src/http/ValidatorFunctions.hpp
Normal file
37
src/http/ValidatorFunctions.hpp
Normal file
@ -0,0 +1,37 @@
|
||||
#ifndef SRC_VALIDATORFUNCTIONS_HPP_
|
||||
#define SRC_VALIDATORFUNCTIONS_HPP_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <string>
|
||||
#undef max
|
||||
#undef min
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include "HTTPValidator.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
/**
|
||||
* This file contains some validator functions that can be used to validate URL parameters.
|
||||
*
|
||||
* They covor common cases like checking for integer, non-empty, ..., so the user of this library
|
||||
* does not need to write them on his own.
|
||||
*/
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
/**
|
||||
* \brief **Built-in validator function**: Checks that a string is not empty.
|
||||
*/
|
||||
bool validateNotEmpty(std::string s);
|
||||
|
||||
/**
|
||||
* \brief **Built-in validator function**: Checks that a value is a positive int
|
||||
*
|
||||
* Checks that the value is a positive integer (combine it with newValidateUnsignedIntegerMax if
|
||||
* you have constraints regarding the size of that number)
|
||||
*/
|
||||
bool validateUnsignedInteger(std::string s);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
254
src/http/WebsocketHandler.cpp
Normal file
254
src/http/WebsocketHandler.cpp
Normal file
@ -0,0 +1,254 @@
|
||||
#include "WebsocketHandler.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
/**
|
||||
* @brief Dump the content of the WebSocket frame for debugging.
|
||||
* @param [in] frame The frame to dump.
|
||||
*/
|
||||
static void dumpFrame(WebsocketFrame frame) {
|
||||
std::string opcode = std::string("Unknown");
|
||||
switch(frame.opCode) {
|
||||
case WebsocketHandler::OPCODE_BINARY: opcode = std::string("BINARY"); break;
|
||||
case WebsocketHandler::OPCODE_CONTINUE: opcode = std::string("CONTINUE"); break;
|
||||
case WebsocketHandler::OPCODE_CLOSE: opcode = std::string("CLOSE"); break;
|
||||
case WebsocketHandler::OPCODE_PING: opcode = std::string("PING"); break;
|
||||
case WebsocketHandler::OPCODE_PONG: opcode = std::string("PONG"); break;
|
||||
case WebsocketHandler::OPCODE_TEXT: opcode = std::string("TEXT"); break;
|
||||
}
|
||||
ESP_LOGI(
|
||||
TAG,
|
||||
"Fin: %d, OpCode: %d (%s), Mask: %d, Len: %d",
|
||||
(int)frame.fin,
|
||||
(int)frame.opCode,
|
||||
opcode.c_str(),
|
||||
(int)frame.mask,
|
||||
(int)frame.len
|
||||
);
|
||||
}
|
||||
|
||||
WebsocketHandler::WebsocketHandler() {
|
||||
_con = nullptr;
|
||||
_receivedClose = false;
|
||||
_sentClose = false;
|
||||
}
|
||||
|
||||
WebsocketHandler::~WebsocketHandler() {
|
||||
|
||||
} // ~WebSocketHandler()
|
||||
|
||||
|
||||
/**
|
||||
* @brief The default onClose handler.
|
||||
* If no over-riding handler is provided for the "close" event, this method is called.
|
||||
*/
|
||||
void WebsocketHandler::onClose() {
|
||||
HTTPS_LOGD("WebsocketHandler close()");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The default onData handler.
|
||||
* If no over-riding handler is provided for the "message" event, this method is called.
|
||||
* A particularly useful pattern for using onMessage is:
|
||||
* ```
|
||||
* std::stringstream buffer;
|
||||
* buffer << pWebSocketInputRecordStreambuf;
|
||||
* ```
|
||||
* This will read the whole message into the string stream.
|
||||
*/
|
||||
void WebsocketHandler::onMessage(WebsocketInputStreambuf* pWebsocketInputStreambuf) { //, Websocket *pWebSocket) {
|
||||
HTTPS_LOGD("WebsocketHandler onMessage()");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief The default onError handler.
|
||||
* If no over-riding handler is provided for the "error" event, this method is called.
|
||||
*/
|
||||
void WebsocketHandler::onError(std::string error) {
|
||||
HTTPS_LOGD("WebsocketHandler onError()");
|
||||
}
|
||||
|
||||
void WebsocketHandler::initialize(ConnectionContext * con) {
|
||||
_con = con;
|
||||
}
|
||||
|
||||
void WebsocketHandler::loop() {
|
||||
if(read() < 0) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
int WebsocketHandler::read() {
|
||||
WebsocketFrame frame;
|
||||
int length = _con->readBuffer((uint8_t*)&frame, sizeof(frame));
|
||||
HTTPS_LOGD("Websocket: Read %d bytes", length);
|
||||
if(length == 0)
|
||||
return 0;
|
||||
else if (length != sizeof(frame)) {
|
||||
HTTPS_LOGE("Websocket read error");
|
||||
//_con->closeConnection();
|
||||
return -1;
|
||||
}
|
||||
dumpFrame(frame);
|
||||
|
||||
// The following section parses the WebSocket frame.
|
||||
uint32_t payloadLen = 0;
|
||||
uint8_t mask[4];
|
||||
if (frame.len < 126) {
|
||||
payloadLen = frame.len;
|
||||
} else if (frame.len == 126) {
|
||||
uint16_t tempLen;
|
||||
_con->readBuffer((uint8_t*)&tempLen, sizeof(tempLen));
|
||||
payloadLen = ntohs(tempLen);
|
||||
} else if (frame.len == 127) {
|
||||
uint64_t tempLen;
|
||||
_con->readBuffer((uint8_t*)&tempLen, sizeof(tempLen));
|
||||
payloadLen = ntohl((uint32_t)tempLen);
|
||||
}
|
||||
if (frame.mask == 1) {
|
||||
_con->readBuffer(mask, sizeof(mask));
|
||||
}
|
||||
|
||||
if (payloadLen == 0) {
|
||||
HTTPS_LOGW("WS payload not present");
|
||||
} else {
|
||||
HTTPS_LOGI("WS payload: length=%d", payloadLen);
|
||||
}
|
||||
|
||||
switch(frame.opCode) {
|
||||
case OPCODE_TEXT:
|
||||
case OPCODE_BINARY: {
|
||||
HTTPS_LOGD("Creating Streambuf");
|
||||
WebsocketInputStreambuf streambuf(_con, payloadLen, frame.mask==1?mask:nullptr);
|
||||
HTTPS_LOGD("Calling onMessage");
|
||||
onMessage(&streambuf);
|
||||
HTTPS_LOGD("Discarding Streambuf");
|
||||
streambuf.discard();
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_CLOSE: { // If the WebSocket operation code is close then we are closing the connection.
|
||||
_receivedClose = true;
|
||||
onClose();
|
||||
//close(); // Close the websocket.
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_CONTINUE: {
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_PING: {
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_PONG: {
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
HTTPS_LOGW("WebSocketReader: Unknown opcode: %d", frame.opCode);
|
||||
break;
|
||||
}
|
||||
} // Switch opCode
|
||||
return 0;
|
||||
} // Websocket::read
|
||||
|
||||
/**
|
||||
* @brief Close the Web socket
|
||||
* @param [in] status The code passed in the close request.
|
||||
* @param [in] message A clarification message on the close request.
|
||||
*/
|
||||
void WebsocketHandler::close(uint16_t status, std::string message) {
|
||||
HTTPS_LOGD("Websocket close()");
|
||||
|
||||
_sentClose = true; // Flag that we have sent a close request.
|
||||
|
||||
WebsocketFrame frame; // Build the web socket frame indicating a close request.
|
||||
frame.fin = 1;
|
||||
frame.rsv1 = 0;
|
||||
frame.rsv2 = 0;
|
||||
frame.rsv3 = 0;
|
||||
frame.opCode = OPCODE_CLOSE;
|
||||
frame.mask = 0;
|
||||
frame.len = message.length() + 2;
|
||||
int rc = _con->writeBuffer((uint8_t *)&frame, sizeof(frame));
|
||||
|
||||
if (rc > 0) {
|
||||
rc = _con->writeBuffer((byte *) &status, 2);
|
||||
}
|
||||
|
||||
if (rc > 0) {
|
||||
_con->writeBuffer((byte *) message.data(), message.length());
|
||||
}
|
||||
} // Websocket::close
|
||||
|
||||
/**
|
||||
* @brief Send data down the web socket
|
||||
* See the WebSocket spec (RFC6455) section "6.1 Sending Data".
|
||||
* We build a WebSocket frame, send the frame followed by the data.
|
||||
* @param [in] data The data to send down the WebSocket.
|
||||
* @param [in] sendType The type of payload. Either SEND_TYPE_TEXT or SEND_TYPE_BINARY.
|
||||
*/
|
||||
void WebsocketHandler::send(std::string data, uint8_t sendType) {
|
||||
HTTPS_LOGD(">> Websocket.send(): length=%d", data.length());
|
||||
WebsocketFrame frame;
|
||||
frame.fin = 1;
|
||||
frame.rsv1 = 0;
|
||||
frame.rsv2 = 0;
|
||||
frame.rsv3 = 0;
|
||||
frame.opCode = sendType==SEND_TYPE_TEXT?OPCODE_TEXT:OPCODE_BINARY;
|
||||
frame.mask = 0;
|
||||
if (data.length() < 126) {
|
||||
frame.len = data.length();
|
||||
_con->writeBuffer((uint8_t *)&frame, sizeof(frame));
|
||||
} else {
|
||||
frame.len = 126;
|
||||
_con->writeBuffer((uint8_t *)&frame, sizeof(frame));
|
||||
uint16_t net_len = htons((uint16_t)data.length());
|
||||
_con->writeBuffer((uint8_t *)&net_len, sizeof(uint16_t)); // Convert to network byte order from host byte order
|
||||
}
|
||||
_con->writeBuffer((uint8_t*)data.data(), data.length());
|
||||
HTTPS_LOGD("<< Websocket.send()");
|
||||
} // Websocket::send
|
||||
|
||||
|
||||
/**
|
||||
* @brief Send data down the web socket
|
||||
* See the WebSocket spec (RFC6455) section "6.1 Sending Data".
|
||||
* We build a WebSocket frame, send the frame followed by the data.
|
||||
* @param [in] data The data to send down the WebSocket.
|
||||
* @param [in] sendType The type of payload. Either SEND_TYPE_TEXT or SEND_TYPE_BINARY.
|
||||
*/
|
||||
void WebsocketHandler::send(uint8_t* data, uint16_t length, uint8_t sendType) {
|
||||
HTTPS_LOGD(">> Websocket.send(): length=%d", length);
|
||||
WebsocketFrame frame;
|
||||
frame.fin = 1;
|
||||
frame.rsv1 = 0;
|
||||
frame.rsv2 = 0;
|
||||
frame.rsv3 = 0;
|
||||
frame.opCode = sendType==SEND_TYPE_TEXT?OPCODE_TEXT:OPCODE_BINARY;
|
||||
frame.mask = 0;
|
||||
if (length < 126) {
|
||||
frame.len = length;
|
||||
_con->writeBuffer((uint8_t *)&frame, sizeof(frame));
|
||||
} else {
|
||||
frame.len = 126;
|
||||
_con->writeBuffer((uint8_t *)&frame, sizeof(frame));
|
||||
uint16_t net_len = htons(length);
|
||||
_con->writeBuffer((uint8_t *)&net_len, sizeof(uint16_t)); // Convert to network byte order from host byte order
|
||||
}
|
||||
_con->writeBuffer(data, length);
|
||||
HTTPS_LOGD("<< Websocket.send()");
|
||||
} // Websocket::send
|
||||
|
||||
/**
|
||||
* Returns true if the connection has been closed, either by client or server
|
||||
*/
|
||||
bool WebsocketHandler::closed() {
|
||||
return _receivedClose || _sentClose;
|
||||
}
|
||||
|
||||
}
|
87
src/http/WebsocketHandler.hpp
Normal file
87
src/http/WebsocketHandler.hpp
Normal file
@ -0,0 +1,87 @@
|
||||
#ifndef SRC_WEBSOCKETHANDLER_HPP_
|
||||
#define SRC_WEBSOCKETHANDLER_HPP_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <lwip/def.h>
|
||||
|
||||
#include <string>
|
||||
#undef min
|
||||
#undef max
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "HTTPSServerConstants.hpp"
|
||||
#include "ConnectionContext.hpp"
|
||||
#include "WebsocketInputStreambuf.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
// Structure definition for the WebSocket frame.
|
||||
struct WebsocketFrame
|
||||
{
|
||||
// Byte 0
|
||||
uint8_t opCode : 4; // [7:4]
|
||||
uint8_t rsv3 : 1; // [3]
|
||||
uint8_t rsv2 : 1; // [2]
|
||||
uint8_t rsv1 : 1; // [1]
|
||||
uint8_t fin : 1; // [0]
|
||||
|
||||
// Byte 1
|
||||
uint8_t len : 7; // [7:1]
|
||||
uint8_t mask : 1; // [0]
|
||||
};
|
||||
|
||||
class WebsocketHandler
|
||||
{
|
||||
public:
|
||||
// WebSocket op codes as found in a WebSocket frame.
|
||||
static const int OPCODE_CONTINUE = 0x00;
|
||||
static const int OPCODE_TEXT = 0x01;
|
||||
static const int OPCODE_BINARY = 0x02;
|
||||
static const int OPCODE_CLOSE = 0x08;
|
||||
static const int OPCODE_PING = 0x09;
|
||||
static const int OPCODE_PONG = 0x0a;
|
||||
|
||||
static const uint16_t CLOSE_NORMAL_CLOSURE = 1000;
|
||||
static const uint16_t CLOSE_GOING_AWAY = 1001;
|
||||
static const uint16_t CLOSE_PROTOCOL_ERROR = 1002;
|
||||
static const uint16_t CLOSE_CANNOT_ACCEPT = 1003;
|
||||
static const uint16_t CLOSE_NO_STATUS_CODE = 1005;
|
||||
static const uint16_t CLOSE_CLOSED_ABNORMALLY = 1006;
|
||||
static const uint16_t CLOSE_NOT_CONSISTENT = 1007;
|
||||
static const uint16_t CLOSE_VIOLATED_POLICY = 1008;
|
||||
static const uint16_t CLOSE_TOO_BIG = 1009;
|
||||
static const uint16_t CLOSE_NO_EXTENSION = 1010;
|
||||
static const uint16_t CLOSE_UNEXPECTED_CONDITION = 1011;
|
||||
static const uint16_t CLOSE_SERVICE_RESTART = 1012;
|
||||
static const uint16_t CLOSE_TRY_AGAIN_LATER = 1013;
|
||||
static const uint16_t CLOSE_TLS_HANDSHAKE_FAILURE = 1015;
|
||||
|
||||
static const uint8_t SEND_TYPE_BINARY = 0x01;
|
||||
static const uint8_t SEND_TYPE_TEXT = 0x02;
|
||||
|
||||
WebsocketHandler();
|
||||
virtual ~WebsocketHandler();
|
||||
virtual void onClose();
|
||||
virtual void onMessage(WebsocketInputStreambuf *pWebsocketInputStreambuf);
|
||||
virtual void onError(std::string error);
|
||||
|
||||
void close(uint16_t status = CLOSE_NORMAL_CLOSURE, std::string message = "");
|
||||
void send(std::string data, uint8_t sendType = SEND_TYPE_BINARY);
|
||||
void send(uint8_t *data, uint16_t length, uint8_t sendType = SEND_TYPE_BINARY);
|
||||
bool closed();
|
||||
|
||||
void loop();
|
||||
void initialize(ConnectionContext * con);
|
||||
|
||||
private:
|
||||
int read();
|
||||
|
||||
ConnectionContext * _con;
|
||||
bool _receivedClose; // True when we have received a close request.
|
||||
bool _sentClose; // True when we have sent a close request.
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
110
src/http/WebsocketInputStreambuf.cpp
Normal file
110
src/http/WebsocketInputStreambuf.cpp
Normal file
@ -0,0 +1,110 @@
|
||||
#include "WebsocketInputStreambuf.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
/**
|
||||
* @brief Create a Web Socket input record streambuf
|
||||
* @param [in] socket The socket we will be reading from.
|
||||
* @param [in] dataLength The size of a record.
|
||||
* @param [in] bufferSize The size of the buffer we wish to allocate to hold data.
|
||||
*/
|
||||
WebsocketInputStreambuf::WebsocketInputStreambuf(
|
||||
ConnectionContext *con,
|
||||
size_t dataLength,
|
||||
uint8_t *pMask,
|
||||
size_t bufferSize
|
||||
) {
|
||||
_con = con; // The socket we will be reading from
|
||||
_dataLength = dataLength; // The size of the record we wish to read.
|
||||
_pMask = pMask;
|
||||
_bufferSize = bufferSize; // The size of the buffer used to hold data
|
||||
_sizeRead = 0; // The size of data read from the socket
|
||||
_buffer = new char[bufferSize]; // Create the buffer used to hold the data read from the socket.
|
||||
|
||||
setg(_buffer, _buffer, _buffer); // Set the initial get buffer pointers to no data.
|
||||
}
|
||||
|
||||
WebsocketInputStreambuf::~WebsocketInputStreambuf() {
|
||||
//FIXME: Call order incorrect? discard() uses _buffer
|
||||
delete[] _buffer;
|
||||
discard();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Discard data for the record that has not yet been read.
|
||||
*
|
||||
* We are working on a logical fixed length record in a socket stream. This means that we know in advance
|
||||
* how big the record should be. If we have read some data from the stream and no longer wish to consume
|
||||
* any further, we have to discard the remaining bytes in the stream before we can get to process the
|
||||
* next record. This function discards the remainder of the data.
|
||||
*
|
||||
* For example, if our record size is 1000 bytes and we have read 700 bytes and determine that we no
|
||||
* longer need to continue, we can't just stop. There are still 300 bytes in the socket stream that
|
||||
* need to be consumed/discarded before we can move on to the next record.
|
||||
*/
|
||||
void WebsocketInputStreambuf::discard() {
|
||||
uint8_t byte;
|
||||
HTTPS_LOGD(">> WebsocketContext.discard(): %d bytes", _dataLength - _sizeRead);
|
||||
while(_sizeRead < _dataLength) {
|
||||
_con->readBuffer(&byte, 1);
|
||||
_sizeRead++;
|
||||
}
|
||||
HTTPS_LOGD("<< WebsocketContext.discard()");
|
||||
} // WebsocketInputStreambuf::discard
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get the size of the expected record.
|
||||
* @return The size of the expected record.
|
||||
*/
|
||||
size_t WebsocketInputStreambuf::getRecordSize() {
|
||||
return _dataLength;
|
||||
} // WebsocketInputStreambuf::getRecordSize
|
||||
|
||||
/**
|
||||
* @brief Handle the request to read data from the stream but we need more data from the source.
|
||||
*
|
||||
*/
|
||||
WebsocketInputStreambuf::int_type WebsocketInputStreambuf::underflow() {
|
||||
HTTPS_LOGD(">> WebSocketInputStreambuf.underflow()");
|
||||
|
||||
// If we have already read as many bytes as our record definition says we should read
|
||||
// then don't attempt to ready any further.
|
||||
if (_sizeRead >= getRecordSize()) {
|
||||
HTTPS_LOGD("<< WebSocketInputStreambuf.underflow(): Already read maximum");
|
||||
return EOF;
|
||||
}
|
||||
|
||||
// We wish to refill the buffer. We want to read data from the socket. We want to read either
|
||||
// the size of the buffer to fill it or the maximum number of bytes remaining to be read.
|
||||
// We will choose which ever is smaller as the number of bytes to read into the buffer.
|
||||
int remainingBytes = getRecordSize()-_sizeRead;
|
||||
size_t sizeToRead;
|
||||
if (remainingBytes < _bufferSize) {
|
||||
sizeToRead = remainingBytes;
|
||||
} else {
|
||||
sizeToRead = _bufferSize;
|
||||
}
|
||||
|
||||
HTTPS_LOGD("WebSocketInputRecordStreambuf - getting next buffer of data; size request: %d", sizeToRead);
|
||||
int bytesRead = _con->readBuffer((uint8_t*)_buffer, sizeToRead);
|
||||
if (bytesRead == 0) {
|
||||
HTTPS_LOGD("<< WebSocketInputRecordStreambuf.underflow(): Read 0 bytes");
|
||||
return EOF;
|
||||
}
|
||||
|
||||
// If the WebSocket frame shows that we have a mask bit set then we have to unmask the data.
|
||||
if (_pMask != nullptr) {
|
||||
for (int i=0; i<bytesRead; i++) {
|
||||
_buffer[i] = _buffer[i] ^ _pMask[(_sizeRead+i)%4];
|
||||
}
|
||||
}
|
||||
|
||||
_sizeRead += bytesRead; // Increase the count of number of bytes actually read from the source.
|
||||
|
||||
setg(_buffer, _buffer, _buffer + bytesRead); // Change the buffer pointers to reflect the new data read.
|
||||
HTTPS_LOGD("<< WebSocketInputRecordStreambuf.underflow(): got %d bytes", bytesRead);
|
||||
return traits_type::to_int_type(*gptr());
|
||||
} // underflow
|
||||
|
||||
}
|
47
src/http/WebsocketInputStreambuf.hpp
Normal file
47
src/http/WebsocketInputStreambuf.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
#ifndef SRC_WEBSOCKETINPUTSTREAMBUF_HPP_
|
||||
#define SRC_WEBSOCKETINPUTSTREAMBUF_HPP_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <lwip/def.h>
|
||||
|
||||
#include <string>
|
||||
#undef min
|
||||
#undef max
|
||||
#include <iostream>
|
||||
#include <streambuf>
|
||||
#include <sstream>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "HTTPSServerConstants.hpp"
|
||||
#include "ConnectionContext.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
class WebsocketInputStreambuf : public std::streambuf {
|
||||
public:
|
||||
WebsocketInputStreambuf(
|
||||
ConnectionContext *con,
|
||||
size_t dataLength,
|
||||
uint8_t *_ = nullptr,
|
||||
size_t bufferSize = 2048
|
||||
);
|
||||
virtual ~WebsocketInputStreambuf();
|
||||
|
||||
int_type underflow();
|
||||
void discard();
|
||||
size_t getRecordSize();
|
||||
|
||||
private:
|
||||
char *_buffer;
|
||||
ConnectionContext *_con;
|
||||
size_t _dataLength;
|
||||
size_t _bufferSize;
|
||||
size_t _sizeRead;
|
||||
uint8_t *_pMask;
|
||||
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
20
src/http/WebsocketNode.cpp
Normal file
20
src/http/WebsocketNode.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
#include "WebsocketNode.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
WebsocketNode::WebsocketNode(const std::string &path, const WebsocketHandlerCreator * creatorFunction, const std::string &tag):
|
||||
HTTPNode(path, WEBSOCKET, tag),
|
||||
_creatorFunction(creatorFunction) {
|
||||
|
||||
}
|
||||
|
||||
WebsocketNode::~WebsocketNode() {
|
||||
|
||||
}
|
||||
|
||||
WebsocketHandler* WebsocketNode::newHandler() {
|
||||
WebsocketHandler * handler = _creatorFunction();
|
||||
return handler;
|
||||
}
|
||||
|
||||
} /* namespace httpsserver */
|
25
src/http/WebsocketNode.hpp
Normal file
25
src/http/WebsocketNode.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
#ifndef SRC_WEBSOCKETNODE_HPP_
|
||||
#define SRC_WEBSOCKETNODE_HPP_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "HTTPNode.hpp"
|
||||
#include "WebsocketHandler.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
typedef WebsocketHandler* (WebsocketHandlerCreator)();
|
||||
|
||||
class WebsocketNode : public HTTPNode {
|
||||
public:
|
||||
WebsocketNode(const std::string &path, const WebsocketHandlerCreator creatorFunction, const std::string &tag = "");
|
||||
virtual ~WebsocketNode();
|
||||
WebsocketHandler* newHandler();
|
||||
std::string getMethod() { return std::string("GET"); }
|
||||
private:
|
||||
const WebsocketHandlerCreator * _creatorFunction;
|
||||
};
|
||||
|
||||
} /* namespace httpsserver */
|
||||
|
||||
#endif /* SRC_WEBSOCKET_HPP_ */
|
98
src/http/util.cpp
Normal file
98
src/http/util.cpp
Normal file
@ -0,0 +1,98 @@
|
||||
#include "util.hpp"
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
uint32_t parseUInt(std::string const &s, uint32_t max) {
|
||||
uint32_t i = 0; // value
|
||||
|
||||
// Check sign
|
||||
size_t x = 0;
|
||||
if (s[0]=='+') {
|
||||
x = 1;
|
||||
}
|
||||
|
||||
// We device max by 10, so we can check if we would exceed it by the next *10 multiplication
|
||||
max/=10;
|
||||
|
||||
// Convert by base 10
|
||||
for(; x < s.size(); x++) {
|
||||
char c = s[x];
|
||||
if (i < max) {
|
||||
if (c >= '0' && c<='9') {
|
||||
i = i*10 + (c-'0');
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
return max;
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
int32_t parseInt(std::string const &s) {
|
||||
uint32_t max = 0x7fffffff;
|
||||
if (s[0]=='-') {
|
||||
return -1 * parseUInt(s.substr(1,max));
|
||||
}
|
||||
return parseUInt(s,max);
|
||||
}
|
||||
|
||||
std::string intToString(int i) {
|
||||
if (i==0) {
|
||||
return "0";
|
||||
}
|
||||
// We need this much digits
|
||||
int digits = ceil(log10(i+1));
|
||||
char c[digits+1];
|
||||
c[digits] = '\0';
|
||||
|
||||
for(int x = digits-1; x >= 0; x--) {
|
||||
char v = (i%10);
|
||||
c[x] = '0' + v;
|
||||
i = (i-v)/10;
|
||||
}
|
||||
|
||||
return std::string(c);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::string urlDecode(std::string input) {
|
||||
std::size_t idxReplaced = 0;
|
||||
// First replace + by space
|
||||
std::size_t idxFound = input.find('+');
|
||||
while (idxFound != std::string::npos) {
|
||||
input.replace(idxFound, 1, 1, ' ');
|
||||
idxFound = input.find('+', idxFound + 1);
|
||||
}
|
||||
// Now replace percent-escapes
|
||||
idxFound = input.find('%');
|
||||
while (idxFound != std::string::npos) {
|
||||
if (idxFound <= input.length() + 3) {
|
||||
char hex[2] = { input[idxFound+1], input[idxFound+2] };
|
||||
byte val = 0;
|
||||
for(int n = 0; n < sizeof(hex); n++) {
|
||||
val <<= 4;
|
||||
if ('0' <= hex[n] && hex[n] <= '9') {
|
||||
val += hex[n]-'0';
|
||||
}
|
||||
else if ('A' <= hex[n] && hex[n] <= 'F') {
|
||||
val += hex[n]-'A'+10;
|
||||
}
|
||||
else if ('a' <= hex[n] && hex[n] <= 'f') {
|
||||
val += hex[n]-'a'+10;
|
||||
}
|
||||
else {
|
||||
goto skipChar;
|
||||
}
|
||||
}
|
||||
input.replace(idxFound, 3, 1, (char)val);
|
||||
}
|
||||
skipChar:
|
||||
idxReplaced = idxFound + 1;
|
||||
idxFound = input.find('%', idxReplaced);
|
||||
}
|
||||
return input;
|
||||
}
|
35
src/http/util.hpp
Normal file
35
src/http/util.hpp
Normal file
@ -0,0 +1,35 @@
|
||||
#ifndef SRC_UTIL_HPP_
|
||||
#define SRC_UTIL_HPP_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
namespace httpsserver {
|
||||
|
||||
/**
|
||||
* \brief **Utility function**: Parse an unsigned integer from a string
|
||||
*
|
||||
* The second parameter can be used to define the maximum value that is acceptable
|
||||
*/
|
||||
uint32_t parseUInt(std::string const &s, uint32_t max = 0xffffffff);
|
||||
|
||||
/**
|
||||
* \brief **Utility function**: Parse a signed integer from a string
|
||||
*/
|
||||
int32_t parseInt(std::string const &s);
|
||||
|
||||
/**
|
||||
* \brief **Utility function**: Transform an int to a std::string
|
||||
*/
|
||||
std::string intToString(int i);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief **Utility function**: Removes URL encoding from the string (e.g. %20 -> space)
|
||||
*/
|
||||
std::string urlDecode(std::string input);
|
||||
|
||||
#endif /* SRC_UTIL_HPP_ */
|
89
src/main.cpp
Normal file
89
src/main.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
#include <Arduino.h>
|
||||
#include "web/web.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <Wire.h>
|
||||
|
||||
Adafruit_Si4713* radio;
|
||||
|
||||
void do_i2c_scan() {
|
||||
Serial.println();
|
||||
Serial.println("I2C scanner. Scanning ...");
|
||||
byte count = 0;
|
||||
|
||||
Wire.begin();
|
||||
for (byte i = 8; i < 120; i++) {
|
||||
Wire.beginTransmission(i); // Begin I2C transmission Address (i)
|
||||
if (Wire.endTransmission() == 0) {
|
||||
// Receive 0 = success (ACK response)
|
||||
Serial.print("Found address: ");
|
||||
Serial.print(i, DEC);
|
||||
Serial.print(" (0x");
|
||||
Serial.print(i, HEX); // PCF8574 7 bit address
|
||||
Serial.println(")");
|
||||
count++;
|
||||
}
|
||||
}
|
||||
Serial.print("Found ");
|
||||
Serial.print(count, DEC); // numbers of devices
|
||||
Serial.println(" device(s).");
|
||||
}
|
||||
|
||||
|
||||
[[noreturn]] void application() {
|
||||
radio = new Adafruit_Si4713(FM_RST_PIN);
|
||||
|
||||
IF_DEBUG {
|
||||
Serial.begin(115200);
|
||||
}
|
||||
|
||||
Serial.println("Adafruit Radio - Si4713 Test");
|
||||
|
||||
if (!radio->begin()) { // begin with address 0x63 (CS high default)
|
||||
Serial.println("Couldn't find radio?");
|
||||
while (1);
|
||||
}
|
||||
|
||||
// Uncomment to scan power of entire range from 87.5 to 108.0 MHz
|
||||
/*
|
||||
for (uint16_t f = 8750; f<10800; f+=10) {
|
||||
radio.readTuneMeasure(f);
|
||||
Serial.print("Measuring "); Serial.print(f); Serial.print("...");
|
||||
radio.readTuneStatus();
|
||||
Serial.println(radio.currNoiseLevel);
|
||||
}
|
||||
*/
|
||||
|
||||
Serial.print("\nSet TX power");
|
||||
radio->setTXpower(114); // dBuV, 88-115 max
|
||||
|
||||
Serial.print("\nTuning into freq");
|
||||
radio->tuneFM(10740); // 100.0 mhz
|
||||
|
||||
// This will tell you the status in case you want to read it from the chip
|
||||
radio->readTuneStatus();
|
||||
Serial.print("\tCurr freq: ");
|
||||
Serial.println(radio->currFreq);
|
||||
Serial.print("\tCurr freqdBuV:");
|
||||
Serial.println(radio->currdBuV);
|
||||
Serial.print("\tCurr ANTcap:");
|
||||
Serial.println(radio->currAntCap);
|
||||
|
||||
// begin the RDS/RDBS transmission
|
||||
radio->beginRDS();
|
||||
radio->setRDSstation((char*)"FUCK YOU!1!");
|
||||
radio->setRDSbuffer( (char*)"Fisting is 300 bucks");
|
||||
|
||||
Serial.println("RDS on!");
|
||||
|
||||
webThread();
|
||||
}
|
||||
|
||||
|
||||
// Остатки ардуиновского мусора
|
||||
|
||||
void setup() {
|
||||
application();
|
||||
// fuck();
|
||||
}
|
||||
void loop() { /* делаем ровно них*я */ }
|
349
src/web/web.cpp
Normal file
349
src/web/web.cpp
Normal file
@ -0,0 +1,349 @@
|
||||
//
|
||||
// Created by vlad on 18.02.2021.
|
||||
//
|
||||
|
||||
#include <WiFi.h>
|
||||
#include "web.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <http/HTTPSServer.hpp>
|
||||
#include <string>
|
||||
|
||||
using namespace httpsserver;
|
||||
|
||||
|
||||
static void getHTML(HTTPRequest * req, HTTPResponse * res) {
|
||||
static char html[] = "<!DOCTYPE html>\n"
|
||||
"<html lang=\"ru\">\n"
|
||||
"<head>\n"
|
||||
" <meta charset=\"UTF-8\">\n"
|
||||
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
|
||||
" <title>Esp32 FM pirate</title>\n"
|
||||
" <style>\n"
|
||||
" * {\n"
|
||||
" color: #7295c4;\n"
|
||||
" border-color: #353535;\n"
|
||||
" background: #121212;\n"
|
||||
" }\n"
|
||||
" h1 {\n"
|
||||
" font-size: 18px;\n"
|
||||
" }\n"
|
||||
" .main {\n"
|
||||
" background: #212121;\n"
|
||||
" height: auto;\n"
|
||||
" }\n"
|
||||
" .main header {\n"
|
||||
" height: auto;\n"
|
||||
" padding: 15px 16px;\n"
|
||||
" justify-content: center;\n"
|
||||
" }\n"
|
||||
" .main .content {\n"
|
||||
" padding: 20px;\n"
|
||||
" }\n"
|
||||
" .main {\n"
|
||||
" border: 3px solid;\n"
|
||||
" border-radius: 8px;\n"
|
||||
" overflow: hidden;\n"
|
||||
" width: 22em;\n"
|
||||
" min-width: 300px;\n"
|
||||
" margin: 50px auto;\n"
|
||||
" height: auto;\n"
|
||||
" text-align: center;\n"
|
||||
" }\n"
|
||||
" .form-row {\n"
|
||||
" padding: 4px 0;\n"
|
||||
" }\n"
|
||||
" .form-row > label {\n"
|
||||
" display: block;\n"
|
||||
" line-height: 2em;\n"
|
||||
" }\n"
|
||||
" .form-row > input {\n"
|
||||
" padding: 8px;\n"
|
||||
" width: 100%;\n"
|
||||
" box-sizing: border-box;\n"
|
||||
" }\n"
|
||||
" input {\n"
|
||||
" border: 2px solid;\n"
|
||||
" border-radius: 4px;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" /* далее стили для слидеров */\n"
|
||||
" .switch {\n"
|
||||
" position: relative;\n"
|
||||
" display: inline-block;\n"
|
||||
" width: 60px;\n"
|
||||
" height: 34px;\n"
|
||||
" margin: 0 auto;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" .switch input {\n"
|
||||
" opacity: 0;\n"
|
||||
" width: 0;\n"
|
||||
" height: 0;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" .slider {\n"
|
||||
" position: absolute;\n"
|
||||
" cursor: pointer;\n"
|
||||
" top: 0;\n"
|
||||
" left: 0;\n"
|
||||
" right: 0;\n"
|
||||
" bottom: 0;\n"
|
||||
" background-color: #ccc;\n"
|
||||
" -webkit-transition: .4s;\n"
|
||||
" transition: .4s;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" .slider:before {\n"
|
||||
" position: absolute;\n"
|
||||
" content: \"\";\n"
|
||||
" height: 26px;\n"
|
||||
" width: 26px;\n"
|
||||
" left: 4px;\n"
|
||||
" bottom: 4px;\n"
|
||||
" background-color: white;\n"
|
||||
" -webkit-transition: .4s;\n"
|
||||
" transition: .4s;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" input:checked + .slider {\n"
|
||||
" background-color: #2196F3;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" input:checked + .slider:before {\n"
|
||||
" transform: translateX(26px);\n"
|
||||
" }\n"
|
||||
" </style>\n"
|
||||
"</head>\n"
|
||||
"<body>\n"
|
||||
"<div class=\"main\">\n"
|
||||
" <header><h1>Lamps4 by Vlados31 & ESP32</h1></header>\n"
|
||||
" <div class=\"content\">\n"
|
||||
" <div id=\"form-content\">\n"
|
||||
"\n"
|
||||
" <div class=\"form-row\">\n"
|
||||
" <label class=\"required\" for=\"control-rds\">RDS текст</label>\n"
|
||||
" <input type=\"text\" maxlength=\"16\" size=\"20\" id=\"control-rds\" onchange=\"sendParam('rds', document.getElementById('control-rds').value)\">\n"
|
||||
" </div>\n"
|
||||
"\n"
|
||||
" <div class=\"form-row\">\n"
|
||||
" <label class=\"required\" for=\"control-frq\">Частота (96.00fm = 9600)</label>\n"
|
||||
" <input type=\"number\" maxlength=\"5\" id=\"control-frq\" onchange=\"sendParam('frq', document.getElementById('control-frq').value)\">\n"
|
||||
" </div>\n"
|
||||
"\n"
|
||||
" <div class=\"form-row\">\n"
|
||||
" <label for=\"control-fmen\">FM Enable</label>\n"
|
||||
" <label class=\"switch\">\n"
|
||||
" <input type=\"checkbox\" id=\"control-fmen\" onchange=\"sendParam('fmen', document.getElementById('control-fmen').checked ? 1 : 0)\">\n"
|
||||
" <span class=\"slider\"></span>\n"
|
||||
" </label>\n"
|
||||
" </div>\n"
|
||||
"\n"
|
||||
" <script>\n"
|
||||
" function sendParam(io_interface, value) {\n"
|
||||
" let xhr = new XMLHttpRequest();\n"
|
||||
" let url = `/`\n"
|
||||
" if (io_interface !== null) {\n"
|
||||
" url += `?${io_interface}=${encodeURIComponent(value)}`\n"
|
||||
" }\n"
|
||||
" xhr.onload = function () {\n"
|
||||
" if (xhr.status !== 200) {\n"
|
||||
" alert(`Ошибка ${xhr.status}: ${xhr.statusText}`);\n"
|
||||
" } else {\n"
|
||||
" let res = JSON.parse(xhr.response)\n"
|
||||
" if (\"frq\" in res) {\n"
|
||||
" document.getElementById('control-frq').value = res[\"frq\"]\n"
|
||||
" }\n"
|
||||
" if (\"rds\" in res) {\n"
|
||||
" document.getElementById('control-rds').value = res[\"rds\"]\n"
|
||||
" }\n"
|
||||
" if (\"fmen\" in res) {\n"
|
||||
" document.getElementById('control-fmen').checked = !!res[\"fmen\"]\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" xhr.open('POST', url)\n"
|
||||
" xhr.send()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" sendParam(null)\n"
|
||||
" </script>\n"
|
||||
" </div>\n"
|
||||
" </div>\n"
|
||||
"</div>\n"
|
||||
"</body>\n"
|
||||
"</html>";
|
||||
// ответим
|
||||
req->discardRequestBody();
|
||||
res->setStatusCode(200);
|
||||
res->setHeader("Content-Type", "text/html; charset=utf-8");
|
||||
res->setHeader("Content-Length", intToString(sizeof(html) - 1));
|
||||
// отправим страничку
|
||||
|
||||
// res->printf(html, pwm_values[0], pwm_values[1], pwm_values[2], pwm_values[3]);
|
||||
res->write((const uint8_t*)html, sizeof(html) - 1);
|
||||
}
|
||||
|
||||
static void getFavicon(HTTPRequest * req, HTTPResponse * res) {
|
||||
static const uint8_t favicon_data[] = {
|
||||
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
|
||||
0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x08, 0x02, 0x00, 0x00, 0x00, 0xFC, 0x18, 0xED,
|
||||
0xA3, 0x00, 0x00, 0x01, 0x84, 0x69, 0x43, 0x43, 0x50, 0x49, 0x43, 0x43, 0x20, 0x70, 0x72, 0x6F,
|
||||
0x66, 0x69, 0x6C, 0x65, 0x00, 0x00, 0x28, 0x91, 0x7D, 0x91, 0x3D, 0x48, 0xC3, 0x40, 0x18, 0x86,
|
||||
0xDF, 0xA6, 0x4A, 0x8B, 0x54, 0x1C, 0xEC, 0x20, 0x22, 0x92, 0xA1, 0x3A, 0x59, 0x10, 0x15, 0x71,
|
||||
0xD4, 0x2A, 0x14, 0xA1, 0x42, 0xA8, 0x15, 0x5A, 0x75, 0x30, 0xB9, 0xF4, 0x0F, 0x9A, 0x34, 0x24,
|
||||
0x29, 0x2E, 0x8E, 0x82, 0x6B, 0xC1, 0xC1, 0x9F, 0xC5, 0xAA, 0x83, 0x8B, 0xB3, 0xAE, 0x0E, 0xAE,
|
||||
0x82, 0x20, 0xF8, 0x03, 0xE2, 0xE8, 0xE4, 0xA4, 0xE8, 0x22, 0x25, 0x7E, 0x97, 0x14, 0x5A, 0xC4,
|
||||
0x78, 0xC7, 0x71, 0x0F, 0xEF, 0x7D, 0xEF, 0xCB, 0xDD, 0x77, 0x80, 0xD0, 0xA8, 0x30, 0xCD, 0xEA,
|
||||
0x1A, 0x07, 0x34, 0xDD, 0x36, 0xD3, 0xC9, 0x84, 0x98, 0xCD, 0xAD, 0x8A, 0xA1, 0x57, 0x04, 0x11,
|
||||
0x42, 0x98, 0xE6, 0xB0, 0xCC, 0x2C, 0x63, 0x4E, 0x92, 0x52, 0xF0, 0x1D, 0x5F, 0xF7, 0x08, 0xF0,
|
||||
0xFD, 0x2E, 0xCE, 0xB3, 0xFC, 0xEB, 0xFE, 0x1C, 0xBD, 0x6A, 0xDE, 0x62, 0x40, 0x40, 0x24, 0x9E,
|
||||
0x65, 0x86, 0x69, 0x13, 0x6F, 0x10, 0x4F, 0x6F, 0xDA, 0x06, 0xE7, 0x7D, 0xE2, 0x28, 0x2B, 0xC9,
|
||||
0x2A, 0xF1, 0x39, 0xF1, 0x98, 0x49, 0x17, 0x24, 0x7E, 0xE4, 0xBA, 0xE2, 0xF1, 0x1B, 0xE7, 0xA2,
|
||||
0xCB, 0x02, 0xCF, 0x8C, 0x9A, 0x99, 0xF4, 0x3C, 0x71, 0x94, 0x58, 0x2C, 0x76, 0xB0, 0xD2, 0xC1,
|
||||
0xAC, 0x64, 0x6A, 0xC4, 0x53, 0xC4, 0x31, 0x55, 0xD3, 0x29, 0x5F, 0xC8, 0x7A, 0xAC, 0x72, 0xDE,
|
||||
0xE2, 0xAC, 0x55, 0x6A, 0xAC, 0x75, 0x4F, 0xFE, 0xC2, 0x48, 0x5E, 0x5F, 0x59, 0xE6, 0x3A, 0xAD,
|
||||
0x21, 0x24, 0xB1, 0x88, 0x25, 0x48, 0x10, 0xA1, 0xA0, 0x86, 0x32, 0x2A, 0xB0, 0x11, 0xA7, 0x5D,
|
||||
0x27, 0xC5, 0x42, 0x9A, 0xCE, 0x13, 0x3E, 0xFE, 0x41, 0xD7, 0x2F, 0x91, 0x4B, 0x21, 0x57, 0x19,
|
||||
0x8C, 0x1C, 0x0B, 0xA8, 0x42, 0x83, 0xEC, 0xFA, 0xC1, 0xFF, 0xE0, 0x77, 0x6F, 0xAD, 0xC2, 0xE4,
|
||||
0x84, 0x97, 0x14, 0x49, 0x00, 0xDD, 0x2F, 0x8E, 0xF3, 0x31, 0x02, 0x84, 0x76, 0x81, 0x66, 0xDD,
|
||||
0x71, 0xBE, 0x8F, 0x1D, 0xA7, 0x79, 0x02, 0x04, 0x9F, 0x81, 0x2B, 0xBD, 0xED, 0xAF, 0x36, 0x80,
|
||||
0x99, 0x4F, 0xD2, 0xEB, 0x6D, 0x2D, 0x76, 0x04, 0xF4, 0x6D, 0x03, 0x17, 0xD7, 0x6D, 0x4D, 0xD9,
|
||||
0x03, 0x2E, 0x77, 0x80, 0x81, 0x27, 0x43, 0x36, 0x65, 0x57, 0x0A, 0xD2, 0x12, 0x0A, 0x05, 0xE0,
|
||||
0xFD, 0x8C, 0xBE, 0x29, 0x07, 0xF4, 0xDF, 0x02, 0x3D, 0x6B, 0x5E, 0xDF, 0x5A, 0xE7, 0x38, 0x7D,
|
||||
0x00, 0x32, 0xD4, 0xAB, 0xD4, 0x0D, 0x70, 0x70, 0x08, 0x8C, 0x16, 0x29, 0x7B, 0xDD, 0xE7, 0xDD,
|
||||
0xE1, 0xCE, 0xBE, 0xFD, 0x5B, 0xD3, 0xEA, 0xDF, 0x0F, 0xFA, 0xE8, 0x72, 0x77, 0xE0, 0x23, 0xEF,
|
||||
0x60, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x2E, 0x23, 0x00, 0x00, 0x2E,
|
||||
0x23, 0x01, 0x78, 0xA5, 0x3F, 0x76, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4D, 0x45, 0x07, 0xE6,
|
||||
0x03, 0x07, 0x08, 0x0C, 0x1F, 0xF9, 0x10, 0x03, 0x17, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58,
|
||||
0x74, 0x43, 0x6F, 0x6D, 0x6D, 0x65, 0x6E, 0x74, 0x00, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
|
||||
0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x47, 0x49, 0x4D, 0x50, 0x57, 0x81, 0x0E, 0x17, 0x00, 0x00,
|
||||
0x00, 0x96, 0x49, 0x44, 0x41, 0x54, 0x48, 0xC7, 0xED, 0x55, 0x51, 0x0E, 0x80, 0x20, 0x08, 0x85,
|
||||
0xD6, 0x09, 0xCC, 0xEB, 0xD8, 0x95, 0xBB, 0x43, 0x5D, 0xA7, 0x3A, 0x82, 0xF4, 0xE9, 0x40, 0xCD,
|
||||
0x5C, 0x6B, 0xD3, 0x06, 0x5F, 0x6C, 0x30, 0x71, 0x3C, 0xDE, 0x7B, 0x48, 0xF0, 0x6D, 0x0C, 0xD0,
|
||||
0xFB, 0x80, 0x31, 0xA4, 0x3B, 0x2B, 0xA0, 0x95, 0xAD, 0xC4, 0x3B, 0x90, 0xD8, 0xE7, 0xE8, 0x30,
|
||||
0xAC, 0xDB, 0xFE, 0x66, 0x45, 0xFD, 0x0F, 0x40, 0x0A, 0xD9, 0xC9, 0x2B, 0x26, 0x02, 0xD9, 0xB1,
|
||||
0x3A, 0x6C, 0x37, 0xEF, 0x92, 0x62, 0xD0, 0x22, 0x93, 0x09, 0x3D, 0x2F, 0x39, 0x8E, 0xDA, 0x1A,
|
||||
0xDD, 0x07, 0x56, 0x4A, 0x45, 0x65, 0x88, 0xA3, 0x02, 0x58, 0x20, 0xA5, 0x15, 0x0A, 0x72, 0x05,
|
||||
0x06, 0x08, 0xF6, 0x09, 0x33, 0x5F, 0xF8, 0x41, 0x41, 0x53, 0xE6, 0x82, 0x4E, 0x08, 0x7B, 0x40,
|
||||
0x65, 0x72, 0x8B, 0x52, 0x21, 0x4C, 0x7F, 0x12, 0xF6, 0xE0, 0x93, 0x44, 0xCD, 0x9A, 0xBE, 0x62,
|
||||
0xD0, 0xA2, 0xE9, 0xEB, 0x8A, 0x32, 0x71, 0x01, 0xBC, 0x3B, 0x1E, 0x5A, 0xC9, 0x99, 0x48, 0x22,
|
||||
0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
|
||||
};
|
||||
// ответим
|
||||
req->discardRequestBody();
|
||||
res->setStatusCode(200);
|
||||
res->setHeader("Content-Type", "image/png");
|
||||
res->setHeader("Content-Length", intToString(sizeof(favicon_data)));
|
||||
// отправим иконку
|
||||
res->write(favicon_data, sizeof(favicon_data));
|
||||
}
|
||||
|
||||
uint32_t output_frq = 8750;
|
||||
std::string output_rds = "FUCK YOU!1!";
|
||||
bool output_enable = false;
|
||||
|
||||
static void setOutputData(HTTPRequest * req, HTTPResponse * res) {
|
||||
auto p = req->getParams();
|
||||
if (p != nullptr) {
|
||||
// сначала частота
|
||||
std::string param;
|
||||
p->getQueryParameter("frq", param);
|
||||
if (!param.empty()) {
|
||||
auto val = parseInt(param);
|
||||
val = min(10800, max(8750, val));
|
||||
output_frq = val;
|
||||
radio->tuneFM(val);
|
||||
// TODO прикрутить запись частоты в передатчик
|
||||
IF_DEBUG Serial.printf("[web] _set_frq: set frq=%d\n", val);
|
||||
}
|
||||
|
||||
// теперь RDS
|
||||
param.clear();
|
||||
p->getQueryParameter("rds", param);
|
||||
if (!param.empty()) {
|
||||
output_rds = param;
|
||||
// TODO прикрутить запись rds в передатчик
|
||||
IF_DEBUG Serial.printf("[web] _set_rds: set rds=\"%s\"\n", param.c_str());
|
||||
}
|
||||
|
||||
// теперь включен/выключен
|
||||
param.clear();
|
||||
p->getQueryParameter("fmen", param);
|
||||
if (!param.empty()) {
|
||||
bool val = parseInt(param) != 0;
|
||||
output_enable = val;
|
||||
// TODO прикрутить запись флага в передатчик
|
||||
IF_DEBUG Serial.printf("[web] _set_fmen: set fmen=%d\n", val);
|
||||
}
|
||||
} else {
|
||||
IF_DEBUG Serial.printf("[web] setOutputData: empty params!\n");
|
||||
}
|
||||
|
||||
// сделаем json ответ со всеми интерфейсами
|
||||
static char response_buffer[1024];
|
||||
sprintf(response_buffer, R"({"frq": %d, "rds": "%s", "fmen": %d})", output_frq, output_rds.c_str(), output_enable);
|
||||
|
||||
// отправим json ответ
|
||||
res->setStatusCode(200);
|
||||
res->setHeader("Content-Type", "application/json; charset=utf-8");
|
||||
auto len = (uint32_t)strlen(response_buffer);
|
||||
res->setHeader("Content-Length", intToString((int32_t)len));
|
||||
|
||||
res->write((const uint8_t*)response_buffer, len);
|
||||
|
||||
IF_DEBUG Serial.printf("[web] send json: %s\n", response_buffer);
|
||||
}
|
||||
|
||||
|
||||
static void handle404(HTTPRequest * req, HTTPResponse * res) {
|
||||
// Discard request body, if we received any
|
||||
// We do this, as this is the default node and may also server POST/PUT requests
|
||||
req->discardRequestBody();
|
||||
IF_DEBUG Serial.printf("[handle404] Resource '%s' not found\n", req->getRequestString().c_str());
|
||||
|
||||
// Set the response status
|
||||
res->setStatusCode(404);
|
||||
res->setStatusText("Not Found");
|
||||
|
||||
// Set content type of the response
|
||||
res->setHeader("Content-Type", "text/html");
|
||||
|
||||
// Write a tiny HTML page
|
||||
res->printf("<!DOCTYPE html>\n"
|
||||
"<html data-theme=\"dark\" lang=\"ru\"><head><meta charset=\"UTF-8\">"
|
||||
"<link rel=\"stylesheet\" href=\"style.css\">"
|
||||
"<title>Not Found</title></head><body><h1>404 Not Found</h1>"
|
||||
"<p>Вы запросили несуществующую страницу...</p></body></html>");
|
||||
}
|
||||
|
||||
[[noreturn]] void webThread() {
|
||||
IF_DEBUG {
|
||||
Serial.printf("******** START SERVER ********\n");
|
||||
}
|
||||
|
||||
// WiFiClass::mode(WIFI_MODE_STA);
|
||||
WiFi.softAP(WIFI_SSID, WIFI_PSK);
|
||||
// WiFi.begin(WIFI_SSID, WIFI_PSK);
|
||||
Serial.printf("[webThread] IP address: %s\n", WiFi.softAPIP().toString().c_str());
|
||||
// Serial.printf("[webThread] IP address: %s\n", WiFi.localIP().toString().c_str());
|
||||
|
||||
HTTPServer server = HTTPServer();
|
||||
// Add all nodes to the server so they become accessible:
|
||||
server.registerNode(new ResourceNode("/", "GET", &getHTML));
|
||||
server.registerNode(new ResourceNode("/favicon.ico", "GET", &getFavicon));
|
||||
server.registerNode(new ResourceNode("/", "POST", &setOutputData));
|
||||
server.setDefaultNode(new ResourceNode("", "GET", &handle404));
|
||||
|
||||
server.start();
|
||||
Serial.printf("[webThread] Server is running\n");
|
||||
|
||||
for (;;) {
|
||||
// This call will let the server do its work
|
||||
server.loop();
|
||||
// Other code would go here...
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
8
src/web/web.h
Normal file
8
src/web/web.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef GBO_WORK0001_ESP32_WEB_H
|
||||
#define GBO_WORK0001_ESP32_WEB_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
[[noreturn]] void webThread();
|
||||
|
||||
#endif //GBO_WORK0001_ESP32_WEB_H
|
Loading…
x
Reference in New Issue
Block a user