Replace base64 files with library. Major refactoring.
This commit is contained in:
@@ -4,6 +4,8 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "wordclock_constants.h"
|
#include "wordclock_constants.h"
|
||||||
|
|
||||||
|
extern bool spiral_direction; // Direction of sprial animation
|
||||||
|
|
||||||
enum Direction
|
enum Direction
|
||||||
{
|
{
|
||||||
RIGHT,
|
RIGHT,
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright (C) 2016 Arturo Guadalupi. All right reserved.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BASE64_WRAPPER_H
|
|
||||||
#define BASE64_WRAPPER_H
|
|
||||||
|
|
||||||
class Base64Class{
|
|
||||||
public:
|
|
||||||
int encode(char *output, char *input, int inputLength);
|
|
||||||
int decode(char * output, char * input, int inputLength);
|
|
||||||
int encodedLength(int plainLength);
|
|
||||||
int decodedLength(char * input, int inputLength);
|
|
||||||
|
|
||||||
private:
|
|
||||||
inline void fromA3ToA4(unsigned char * A4, unsigned char * A3);
|
|
||||||
inline void fromA4ToA3(unsigned char * A3, unsigned char * A4);
|
|
||||||
inline unsigned char lookupTable(char c);
|
|
||||||
};
|
|
||||||
extern Base64Class Base64;
|
|
||||||
|
|
||||||
#endif /* BASE64_WRAPPER_H */
|
|
||||||
@@ -43,10 +43,10 @@ private:
|
|||||||
// current representation of matrix as 2D array
|
// current representation of matrix as 2D array
|
||||||
uint32_t _current_grid[MATRIX_HEIGHT][MATRIX_WIDTH] = {0};
|
uint32_t _current_grid[MATRIX_HEIGHT][MATRIX_WIDTH] = {0};
|
||||||
|
|
||||||
// target representation of minutes indicator leds
|
// target representation of minutes indicator LEDs
|
||||||
uint32_t _target_minute_indicators[4] = {0, 0, 0, 0};
|
uint32_t _target_minute_indicators[4] = {0, 0, 0, 0};
|
||||||
|
|
||||||
// current representation of minutes indicator leds
|
// current representation of minutes indicator LEDs
|
||||||
uint32_t _current_minute_indicators[4] = {0, 0, 0, 0};
|
uint32_t _current_minute_indicators[4] = {0, 0, 0, 0};
|
||||||
|
|
||||||
void _draw_on_matrix(float factor);
|
void _draw_on_matrix(float factor);
|
||||||
|
|||||||
@@ -67,4 +67,12 @@
|
|||||||
#define MATRIX_WIDTH (11)
|
#define MATRIX_WIDTH (11)
|
||||||
#define MATRIX_HEIGHT (11)
|
#define MATRIX_HEIGHT (11)
|
||||||
|
|
||||||
|
// NTP macros
|
||||||
|
#define BUILD_YEAR (__DATE__ + 7) /* Will expand to current year at compile time as string. */
|
||||||
|
#define NTP_MININUM_RX_YEAR (atoi(BUILD_YEAR) - 1) /* Will expand to current year at compile time minus one. */
|
||||||
|
#define NTP_MININUM_YEAR (1900) // NTP minimum year is 1900
|
||||||
|
#define NTP_MAX_UPDATE_TIME_US (500000) // 500ms max update time
|
||||||
|
#define NTP_NEXT_UPDATE_DELAY_US (10000000) // 10s delay time between updates
|
||||||
|
#define NTP_WATCHDOG_COUNTER_INIT (30) // Watchdog value, count of retries before restart
|
||||||
|
|
||||||
#endif /* WORDCLOCK_CONSTANTS_H */
|
#endif /* WORDCLOCK_CONSTANTS_H */
|
||||||
|
|||||||
@@ -12,13 +12,9 @@
|
|||||||
|
|
||||||
#define EEPROM_SIZE (sizeof(EepromLayout_st) / sizeof(uint8_t))
|
#define EEPROM_SIZE (sizeof(EepromLayout_st) / sizeof(uint8_t))
|
||||||
|
|
||||||
#define BUILD_YEAR (__DATE__ + 7) /* Will expand to current year at compile time as string. */
|
// ----------------------------------------------------------------------------------
|
||||||
#define NTP_MININUM_RX_YEAR (atoi(BUILD_YEAR) - 1) /* Will expand to current year at compile time minus one. */
|
// TYPEDEFS
|
||||||
#define NTP_MININUM_YEAR (1900) // NTP minimum year is 1900
|
// ----------------------------------------------------------------------------------
|
||||||
#define NTP_MAX_UPDATE_TIME_US (500000) // 500ms max update time
|
|
||||||
#define NTP_NEXT_UPDATE_DELAY_US (10000000) // 10s delay time between updates
|
|
||||||
#define NTP_WATCHDOG_COUNTER_INIT (30) // Watchdog value, count of retries before restart
|
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
int start_hour;
|
int start_hour;
|
||||||
@@ -32,7 +28,7 @@ typedef struct
|
|||||||
uint8_t red;
|
uint8_t red;
|
||||||
uint8_t green;
|
uint8_t green;
|
||||||
uint8_t blue;
|
uint8_t blue;
|
||||||
uint8_t alpha;
|
uint8_t alpha; // note: unused
|
||||||
} Color_st;
|
} Color_st;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
@@ -62,12 +58,16 @@ typedef enum
|
|||||||
NUM_STATES
|
NUM_STATES
|
||||||
} ClockState_en;
|
} ClockState_en;
|
||||||
|
|
||||||
bool get_ntp_time(uint32 usec);
|
// ----------------------------------------------------------------------------------
|
||||||
|
// FUNCTIONS DECLARATIONS
|
||||||
|
// ----------------------------------------------------------------------------------
|
||||||
|
bool get_ntp_time(uint32 timeout);
|
||||||
String leading_zero2digit(int value);
|
String leading_zero2digit(int value);
|
||||||
uint8_t calculate_dynamic_brightness(uint8_t min_brightness, uint8_t max_brightness, int hours, int minutes, bool summertime);
|
uint8_t calculate_dynamic_brightness(uint8_t min_brightness, uint8_t max_brightness, int hours, int minutes, bool summertime);
|
||||||
uint8_t update_brightness(void);
|
uint8_t update_brightness(void);
|
||||||
void check_night_mode(void);
|
void check_night_mode(void);
|
||||||
void check_wifi_status(void);
|
void check_wifi_status(void);
|
||||||
|
void cold_start_setup(void);
|
||||||
void draw_main_color(void);
|
void draw_main_color(void);
|
||||||
void handle_button(void);
|
void handle_button(void);
|
||||||
void handle_command(void);
|
void handle_command(void);
|
||||||
@@ -75,6 +75,7 @@ void handle_current_state(void);
|
|||||||
void handle_data_request(void);
|
void handle_data_request(void);
|
||||||
void handle_led_direct(void);
|
void handle_led_direct(void);
|
||||||
void limit_value_ranges(void);
|
void limit_value_ranges(void);
|
||||||
|
void log_data(void);
|
||||||
void log_time(tm local_time);
|
void log_time(tm local_time);
|
||||||
void ntp_time_update(uint32 max_update_time);
|
void ntp_time_update(uint32 max_update_time);
|
||||||
void on_state_entry(uint8_t state);
|
void on_state_entry(uint8_t state);
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ platform = espressif8266
|
|||||||
board = nodemcuv2
|
board = nodemcuv2
|
||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps =
|
lib_deps =
|
||||||
adafruit/Adafruit BusIO@^1.15.0
|
adafruit/Adafruit BusIO@^1.15.0
|
||||||
adafruit/Adafruit NeoMatrix@^1.3.0
|
adafruit/Adafruit NeoMatrix@^1.3.0
|
||||||
adafruit/Adafruit NeoPixel@^1.11.0
|
adafruit/Adafruit NeoPixel@^1.11.0
|
||||||
tzapu/WiFiManager@^0.16.0
|
densaugeo/base64@^1.4.0
|
||||||
|
tzapu/WiFiManager@^0.16.0
|
||||||
|
|
||||||
[env:nodemcuv2]
|
[env:nodemcuv2]
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ extern LEDMatrix led_matrix;
|
|||||||
const int8_t dx[] = {1, -1, 0, 0};
|
const int8_t dx[] = {1, -1, 0, 0};
|
||||||
const int8_t dy[] = {0, 0, -1, 1};
|
const int8_t dy[] = {0, 0, -1, 1};
|
||||||
|
|
||||||
|
bool spiral_direction = false; // Direction of sprial animation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Function to draw a spiral step (from center)
|
* @brief Function to draw a spiral step (from center)
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -26,7 +26,8 @@
|
|||||||
#include <Adafruit_GFX.h> // https://github.com/adafruit/Adafruit-GFX-Library
|
#include <Adafruit_GFX.h> // https://github.com/adafruit/Adafruit-GFX-Library
|
||||||
#include <Adafruit_NeoMatrix.h> // https://github.com/adafruit/Adafruit_NeoMatrix
|
#include <Adafruit_NeoMatrix.h> // https://github.com/adafruit/Adafruit_NeoMatrix
|
||||||
#include <Adafruit_NeoPixel.h> // NeoPixel library used to run the NeoPixel LEDs: https://github.com/adafruit/Adafruit_NeoPixel
|
#include <Adafruit_NeoPixel.h> // NeoPixel library used to run the NeoPixel LEDs: https://github.com/adafruit/Adafruit_NeoPixel
|
||||||
#include <EEPROM.h> //from ESP8266 Arduino Core (automatically installed when ESP8266 was installed via Boardmanager)
|
#include <base64.hpp>
|
||||||
|
#include <EEPROM.h> //from ESP8266 Arduino Core (automatically installed when ESP8266 was installed via Boardmanager)
|
||||||
#include <ESP8266WebServer.h>
|
#include <ESP8266WebServer.h>
|
||||||
#include <ESP8266WiFi.h>
|
#include <ESP8266WiFi.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
@@ -34,7 +35,6 @@
|
|||||||
|
|
||||||
// own libraries
|
// own libraries
|
||||||
#include "animation_functions.h"
|
#include "animation_functions.h"
|
||||||
#include "base64_wrapper.h" // copied from https://github.com/Xander-Electronics/Base64
|
|
||||||
#include "led_matrix.h"
|
#include "led_matrix.h"
|
||||||
#include "littlefs_wrapper.h"
|
#include "littlefs_wrapper.h"
|
||||||
#include "ota_functions.h"
|
#include "ota_functions.h"
|
||||||
@@ -54,43 +54,43 @@ Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(MATRIX_WIDTH, MATRIX_HEIGHT + 1,
|
|||||||
NEO_GRB + NEO_KHZ800); // NeoMatrix
|
NEO_GRB + NEO_KHZ800); // NeoMatrix
|
||||||
ESP8266WebServer webserver(HTTP_PORT); // Webserver
|
ESP8266WebServer webserver(HTTP_PORT); // Webserver
|
||||||
LEDMatrix led_matrix = LEDMatrix(&matrix, DEFAULT_BRIGHTNESS, &logger); // NeoMatrix wrapper
|
LEDMatrix led_matrix = LEDMatrix(&matrix, DEFAULT_BRIGHTNESS, &logger); // NeoMatrix wrapper
|
||||||
struct tm time_info; // Structure tm holds time information
|
|
||||||
time_t time_now; // Seconds since Epoch (1970) - UTC
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------
|
||||||
// STATIC VARIABLES
|
// STATIC VARIABLES
|
||||||
// ----------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------
|
||||||
|
// EEPROM values
|
||||||
static EepromLayout_st eeprom_buffer = {{0, 0, 0, 0}, {0U, 0U, 0U, false}, {0U, 0U, 0U, 0U}};
|
static EepromLayout_st eeprom_buffer = {{0, 0, 0, 0}, {0U, 0U, 0U, false}, {0U, 0U, 0U, 0U}};
|
||||||
static Brightness_st *brightness_ps = &eeprom_buffer.brightness_values;
|
static Brightness_st *const brightness_ps = &eeprom_buffer.brightness_values;
|
||||||
static Color_st *colors_ps = &eeprom_buffer.color_values;
|
static Color_st *const colors_ps = &eeprom_buffer.color_values;
|
||||||
static NightModeTimes_st *night_mode_times_ps = &eeprom_buffer.night_mode_times;
|
static NightModeTimes_st *const night_mode_times_ps = &eeprom_buffer.night_mode_times;
|
||||||
|
|
||||||
|
// Games
|
||||||
static Pong pong = Pong(&led_matrix, &logger);
|
static Pong pong = Pong(&led_matrix, &logger);
|
||||||
static Snake snake = Snake(&led_matrix, &logger);
|
static Snake snake = Snake(&led_matrix, &logger);
|
||||||
static Tetris tetris = Tetris(&led_matrix, &logger);
|
static Tetris tetris = Tetris(&led_matrix, &logger);
|
||||||
|
|
||||||
static uint32 last_ntp_update_us = 0; // Time of last NTP update
|
// Time
|
||||||
static char strftime_buf[64]; // Time string buffer
|
static struct tm time_info; // Structure tm holds time information
|
||||||
|
static time_t time_now; // Seconds since Epoch (1970) - UTC
|
||||||
|
|
||||||
static bool flg_night_mode = false; // State of nightmode
|
// NTP
|
||||||
static bool flg_reset_wifi_creds = false; // Used to reset stored wifi credentials
|
static uint32 last_ntp_update_us = 0; // Time of last NTP update
|
||||||
static bool spiral_direction = false;
|
|
||||||
|
// State variables
|
||||||
|
static bool flg_night_mode = false; // State of nightmode
|
||||||
|
static bool flg_reset_wifi_creds = false; // Used to reset stored wifi credentials
|
||||||
static float filter_factor = DEFAULT_SMOOTHING_FACTOR; // Stores smoothing factor for led transition, value of 1 represents no smoothing.
|
static float filter_factor = DEFAULT_SMOOTHING_FACTOR; // Stores smoothing factor for led transition, value of 1 represents no smoothing.
|
||||||
static int watchdog_counter = 30; // Watchdog counter to trigger restart if NTP update was not possible 30 times in a row (5min)
|
|
||||||
static uint32 last_led_direct_us = 0; // Time of last direct LED command (=> fall back to normal mode after timeout)
|
|
||||||
static uint32_t heartbeat_counter = 0; // Heartbeat on-time in seconds
|
|
||||||
static uint32_t main_color_clock = colors_24bit[2]; // Color of the clock and digital clock
|
static uint32_t main_color_clock = colors_24bit[2]; // Color of the clock and digital clock
|
||||||
static uint32_t main_color_snake = colors_24bit[1]; // Color of the random snake animation
|
|
||||||
static uint8_t current_brightness = DEFAULT_BRIGHTNESS; // Current brightness of LEDs
|
static uint8_t current_brightness = DEFAULT_BRIGHTNESS; // Current brightness of LEDs
|
||||||
static uint8_t current_state = (uint8_t)ST_CLOCK; // Stores current state
|
static uint8_t current_state = (uint8_t)ST_CLOCK; // Stores current state
|
||||||
|
|
||||||
static const String state_names[NUM_STATES] = {"Clock", "DiClock", "Spiral", "Tetris", "Snake", "PingPong", "Hearts"};
|
// Other variables
|
||||||
static const uint32_t period_timings[NUM_STATES] = {PERIOD_CLOCK_UPDATE_US, PERIOD_CLOCK_UPDATE_US,
|
static uint32 last_led_direct_us = 0; // Time of last direct LED command (=> fall back to normal mode after timeout)
|
||||||
PERIOD_ANIMATION_US, PERIOD_TETRIS_US, PERIOD_SNAKE_US,
|
static uint32_t heartbeat_counter = 0; // Heartbeat on-time in seconds
|
||||||
PERIOD_PONG_US, PERIOD_ANIMATION_US};
|
|
||||||
|
|
||||||
// Quarterly brightness factor for dynamic brightness (4 quarters a 24 hours)
|
// Const definitions
|
||||||
static const float qtly_brightness_factor[96] = {
|
static const String state_names[NUM_STATES] = {"Clock", "DiClock", "Spiral", "Tetris", "Snake", "PingPong", "Hearts"}; // all clock states
|
||||||
|
static const float qtly_brightness_factor[96] = { // Quarterly brightness factor for dynamic brightness (4 quarters a 24 hours)
|
||||||
0.0f, 0.0f, 0.0f, 0.001f, 0.003f, 0.007f, 0.014f, 0.026f, 0.044f, 0.069f, 0.101f, 0.143f, 0.194f, 0.253f, 0.32f,
|
0.0f, 0.0f, 0.0f, 0.001f, 0.003f, 0.007f, 0.014f, 0.026f, 0.044f, 0.069f, 0.101f, 0.143f, 0.194f, 0.253f, 0.32f,
|
||||||
0.392f, 0.468f, 0.545f, 0.62f, 0.691f, 0.755f, 0.811f, 0.858f, 0.896f, 0.927f, 0.949f, 0.966f, 0.978f, 0.986f,
|
0.392f, 0.468f, 0.545f, 0.62f, 0.691f, 0.755f, 0.811f, 0.858f, 0.896f, 0.927f, 0.949f, 0.966f, 0.978f, 0.986f,
|
||||||
0.991f, 0.995f, 0.997f, 0.998f, 0.999f, 0.999f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
|
0.991f, 0.995f, 0.997f, 0.998f, 0.999f, 0.999f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
|
||||||
@@ -98,7 +98,9 @@ static const float qtly_brightness_factor[96] = {
|
|||||||
0.998f, 0.997f, 0.995f, 0.991f, 0.986f, 0.978f, 0.966f, 0.949f, 0.927f, 0.896f, 0.858f, 0.811f, 0.755f, 0.691f,
|
0.998f, 0.997f, 0.995f, 0.991f, 0.986f, 0.978f, 0.966f, 0.949f, 0.927f, 0.896f, 0.858f, 0.811f, 0.755f, 0.691f,
|
||||||
0.62f, 0.545f, 0.468f, 0.392f, 0.32f, 0.253f, 0.194f, 0.143f, 0.101f, 0.069f, 0.044f, 0.026f, 0.014f, 0.007f,
|
0.62f, 0.545f, 0.468f, 0.392f, 0.32f, 0.253f, 0.194f, 0.143f, 0.101f, 0.069f, 0.044f, 0.026f, 0.014f, 0.007f,
|
||||||
0.003f, 0.001f, 0.0f, 0.0f};
|
0.003f, 0.001f, 0.0f, 0.0f};
|
||||||
|
static const uint32_t period_timings[NUM_STATES] = {PERIOD_CLOCK_UPDATE_US, PERIOD_CLOCK_UPDATE_US,
|
||||||
|
PERIOD_ANIMATION_US, PERIOD_TETRIS_US, PERIOD_SNAKE_US,
|
||||||
|
PERIOD_PONG_US, PERIOD_ANIMATION_US};
|
||||||
// ----------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------
|
||||||
// SETUP
|
// SETUP
|
||||||
// ----------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------
|
||||||
@@ -112,10 +114,10 @@ void setup()
|
|||||||
Serial.println();
|
Serial.println();
|
||||||
|
|
||||||
// Reset info
|
// Reset info
|
||||||
rst_info *resetInfo = ESP.getResetInfoPtr();
|
rst_info *reset_info = ESP.getResetInfoPtr();
|
||||||
Serial.printf("Reset reason: %u\n", resetInfo->reason);
|
Serial.printf("Reset reason: %u\n", reset_info->reason);
|
||||||
Serial.printf("Reset cause: %u\n", resetInfo->exccause);
|
Serial.printf("Reset cause: %u\n", reset_info->exccause);
|
||||||
Serial.printf("Reset address: %u\n", resetInfo->excvaddr);
|
Serial.printf("Reset address: %u\n", reset_info->excvaddr);
|
||||||
Serial.println();
|
Serial.println();
|
||||||
|
|
||||||
// Init EEPROM
|
// Init EEPROM
|
||||||
@@ -134,7 +136,7 @@ void setup()
|
|||||||
led_matrix.setup_matrix();
|
led_matrix.setup_matrix();
|
||||||
led_matrix.set_current_limit(CURRENT_LIMIT_LED);
|
led_matrix.set_current_limit(CURRENT_LIMIT_LED);
|
||||||
|
|
||||||
// Turn on minutes leds (blue)
|
// Turn on minutes LEDs (blue)
|
||||||
led_matrix.set_min_indicator(15, colors_24bit[6]);
|
led_matrix.set_min_indicator(15, colors_24bit[6]);
|
||||||
led_matrix.draw_on_matrix_instant();
|
led_matrix.draw_on_matrix_instant();
|
||||||
|
|
||||||
@@ -148,11 +150,12 @@ void setup()
|
|||||||
// If you get here you have connected to the WiFi
|
// If you get here you have connected to the WiFi
|
||||||
Serial.printf("Connected, IP address: ");
|
Serial.printf("Connected, IP address: ");
|
||||||
Serial.println(WiFi.localIP());
|
Serial.println(WiFi.localIP());
|
||||||
|
|
||||||
// ESP8266 tries to reconnect automatically when the connection is lost
|
// ESP8266 tries to reconnect automatically when the connection is lost
|
||||||
WiFi.setAutoReconnect(true);
|
WiFi.setAutoReconnect(true);
|
||||||
WiFi.persistent(true);
|
WiFi.persistent(true);
|
||||||
|
|
||||||
// Turn off minutes leds
|
// Turn off minutes LEDs
|
||||||
led_matrix.set_min_indicator(15, 0);
|
led_matrix.set_min_indicator(15, 0);
|
||||||
led_matrix.draw_on_matrix_instant();
|
led_matrix.draw_on_matrix_instant();
|
||||||
|
|
||||||
@@ -169,44 +172,10 @@ void setup()
|
|||||||
|
|
||||||
// create UDP Logger to send logging messages via UDP multicast
|
// create UDP Logger to send logging messages via UDP multicast
|
||||||
logger = UDPLogger(WiFi.localIP(), LOGGER_MULTICAST_IP, LOGGER_MULTICAST_PORT, "Wordclock 2.0");
|
logger = UDPLogger(WiFi.localIP(), LOGGER_MULTICAST_IP, LOGGER_MULTICAST_PORT, "Wordclock 2.0");
|
||||||
logger.log_string("Start program\n");
|
|
||||||
logger.log_string("Sketchname: " + String(__FILE__));
|
|
||||||
logger.log_string("Build: " + String(__TIMESTAMP__));
|
|
||||||
logger.log_string("IP: " + WiFi.localIP().toString());
|
|
||||||
logger.log_string("Reset Reason: " + ESP.getResetReason());
|
|
||||||
|
|
||||||
if (resetInfo->reason != REASON_SOFT_RESTART) // only if there was a cold start/hard reset
|
if (reset_info->reason != REASON_SOFT_RESTART) // only if there was a cold start/hard reset
|
||||||
{
|
{
|
||||||
// quickly test each LED
|
cold_start_setup();
|
||||||
for (int16_t row = 0; row < MATRIX_HEIGHT; row++)
|
|
||||||
{
|
|
||||||
for (int16_t col = 0; col < MATRIX_WIDTH; col++)
|
|
||||||
{
|
|
||||||
matrix.fillScreen(0);
|
|
||||||
matrix.drawPixel(col, row, LEDMatrix::color_24_to_16bit(colors_24bit[2]));
|
|
||||||
matrix.show();
|
|
||||||
delay(10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear Matrix
|
|
||||||
matrix.fillScreen(0);
|
|
||||||
matrix.show();
|
|
||||||
delay(200);
|
|
||||||
|
|
||||||
// display IP
|
|
||||||
uint8_t address = WiFi.localIP()[3];
|
|
||||||
led_matrix.print_char(1, 0, 'I', main_color_clock);
|
|
||||||
led_matrix.print_char(5, 0, 'P', main_color_clock);
|
|
||||||
led_matrix.print_number(0, 6, (address / 100), main_color_clock);
|
|
||||||
led_matrix.print_number(4, 6, (address / 10) % 10, main_color_clock);
|
|
||||||
led_matrix.print_number(8, 6, address % 10, main_color_clock);
|
|
||||||
led_matrix.draw_on_matrix_instant();
|
|
||||||
delay(2000);
|
|
||||||
|
|
||||||
// clear matrix
|
|
||||||
led_matrix.flush();
|
|
||||||
led_matrix.draw_on_matrix_instant();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup NTP
|
// setup NTP
|
||||||
@@ -230,12 +199,11 @@ void setup()
|
|||||||
// Set range limits
|
// Set range limits
|
||||||
limit_value_ranges();
|
limit_value_ranges();
|
||||||
|
|
||||||
logger.log_string("Nightmode starts at: " + String(night_mode_times_ps->start_hour) + ":" + String(night_mode_times_ps->start_min));
|
|
||||||
logger.log_string("Nightmode ends at: " + String(night_mode_times_ps->end_hour) + ":" + String(night_mode_times_ps->end_min));
|
|
||||||
|
|
||||||
// Update brightness
|
// Update brightness
|
||||||
current_brightness = update_brightness();
|
current_brightness = update_brightness();
|
||||||
logger.log_string("Brightness: " + String(((uint16_t)current_brightness * 100) / UINT8_MAX) + "%");
|
|
||||||
|
// Send logging data
|
||||||
|
log_data();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------
|
||||||
@@ -305,89 +273,155 @@ void loop()
|
|||||||
// ----------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------
|
||||||
// OTHER FUNCTIONS
|
// OTHER FUNCTIONS
|
||||||
// ----------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @brief Log information
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void log_data()
|
||||||
|
{
|
||||||
|
logger.log_string("Start program\n");
|
||||||
|
logger.log_string("Sketchname: " + String(__FILE__));
|
||||||
|
logger.log_string("Build: " + String(__TIMESTAMP__));
|
||||||
|
logger.log_string("IP: " + WiFi.localIP().toString());
|
||||||
|
logger.log_string("Reset Reason: " + ESP.getResetReason());
|
||||||
|
|
||||||
|
logger.log_string("Nightmode starts at: " + String(night_mode_times_ps->start_hour) + ":" + String(night_mode_times_ps->start_min));
|
||||||
|
logger.log_string("Nightmode ends at: " + String(night_mode_times_ps->end_hour) + ":" + String(night_mode_times_ps->end_min));
|
||||||
|
|
||||||
|
logger.log_string("Brightness: " + String(((uint16_t)current_brightness * 100) / UINT8_MAX) + "%");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test all LEDs and display IP address
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void cold_start_setup()
|
||||||
|
{
|
||||||
|
// quickly test each LED
|
||||||
|
for (int16_t row = 0; row < MATRIX_HEIGHT; row++)
|
||||||
|
{
|
||||||
|
for (int16_t col = 0; col < MATRIX_WIDTH; col++)
|
||||||
|
{
|
||||||
|
matrix.fillScreen(0);
|
||||||
|
matrix.drawPixel(col, row, LEDMatrix::color_24_to_16bit(colors_24bit[2]));
|
||||||
|
matrix.show();
|
||||||
|
delay(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear Matrix
|
||||||
|
matrix.fillScreen(0);
|
||||||
|
matrix.show();
|
||||||
|
delay(200);
|
||||||
|
|
||||||
|
// display IP
|
||||||
|
uint8_t address = WiFi.localIP()[3];
|
||||||
|
led_matrix.print_char(1, 0, 'I', main_color_clock);
|
||||||
|
led_matrix.print_char(5, 0, 'P', main_color_clock);
|
||||||
|
led_matrix.print_number(0, 6, (address / 100), main_color_clock);
|
||||||
|
led_matrix.print_number(4, 6, (address / 10) % 10, main_color_clock);
|
||||||
|
led_matrix.print_number(8, 6, address % 10, main_color_clock);
|
||||||
|
led_matrix.draw_on_matrix_instant();
|
||||||
|
delay(2000);
|
||||||
|
|
||||||
|
// clear matrix
|
||||||
|
led_matrix.flush();
|
||||||
|
led_matrix.draw_on_matrix_instant();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Updates the NTP time
|
* @brief Updates the NTP time
|
||||||
*
|
*
|
||||||
* @return boolean - true if NTP update was successful, false otherwise
|
* @return boolean - true if NTP update was successful, false otherwise
|
||||||
*/
|
*/
|
||||||
bool get_ntp_time(uint32 usec)
|
bool get_ntp_time(uint32 timeout)
|
||||||
{
|
{
|
||||||
uint32 start_time_us = system_get_time();
|
uint32 start_time_us = system_get_time();
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
time(&time_now);
|
time(&time_now);
|
||||||
localtime_r(&time_now, &time_info);
|
localtime_r(&time_now, &time_info);
|
||||||
delay(10);
|
yield();
|
||||||
} while (((system_get_time() - start_time_us) <= usec) && (time_info.tm_year < (NTP_MININUM_RX_YEAR - NTP_MININUM_YEAR)));
|
} while (((system_get_time() - start_time_us) <= timeout) && (time_info.tm_year < (NTP_MININUM_RX_YEAR - NTP_MININUM_YEAR)));
|
||||||
|
|
||||||
|
logger.log_string(String("NTP-Update duration: " + String(system_get_time() - start_time_us) + String("us")));
|
||||||
|
|
||||||
return ((time_info.tm_year <= (NTP_MININUM_RX_YEAR - NTP_MININUM_YEAR)) ? false : true);
|
return ((time_info.tm_year <= (NTP_MININUM_RX_YEAR - NTP_MININUM_YEAR)) ? false : true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Log local_time.
|
||||||
|
*
|
||||||
|
* @param local_time
|
||||||
|
*/
|
||||||
void log_time(tm local_time)
|
void log_time(tm local_time)
|
||||||
{
|
{
|
||||||
|
char strftime_buf[64]; // Time string buffer
|
||||||
strftime(strftime_buf, sizeof(strftime_buf), "%c", &time_info);
|
strftime(strftime_buf, sizeof(strftime_buf), "%c", &time_info);
|
||||||
logger.log_string(String(strftime_buf));
|
logger.log_string(String(strftime_buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Update and control word clock states.
|
||||||
|
*/
|
||||||
void handle_current_state()
|
void handle_current_state()
|
||||||
{
|
{
|
||||||
switch (current_state)
|
switch (current_state)
|
||||||
{
|
{
|
||||||
case ST_CLOCK: // state clock
|
case ST_CLOCK: // state clock
|
||||||
|
{
|
||||||
|
(void)show_string_on_clock(time_to_string((uint8_t)time_info.tm_hour, (uint8_t)time_info.tm_min), main_color_clock);
|
||||||
|
draw_minute_indicator((uint8_t)time_info.tm_min, main_color_clock);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ST_DICLOCK: // state diclock
|
||||||
|
{
|
||||||
|
show_digital_clock((uint8_t)time_info.tm_hour, (uint8_t)time_info.tm_min, main_color_clock);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ST_SPIRAL: // state spiral
|
||||||
|
{
|
||||||
|
int res = draw_spiral(false, spiral_direction, MATRIX_WIDTH - 2);
|
||||||
|
if ((bool)res && spiral_direction == 0)
|
||||||
{
|
{
|
||||||
(void)show_string_on_clock(time_to_string((uint8_t)time_info.tm_hour, (uint8_t)time_info.tm_min), main_color_clock);
|
// change spiral direction to closing (draw empty LEDs)
|
||||||
draw_minute_indicator((uint8_t)time_info.tm_min, main_color_clock);
|
spiral_direction = true;
|
||||||
break;
|
// init spiral with new spiral direction
|
||||||
|
draw_spiral(true, spiral_direction, MATRIX_WIDTH - 1);
|
||||||
}
|
}
|
||||||
case ST_DICLOCK: // state diclock
|
else if (res && spiral_direction == 1)
|
||||||
{
|
{
|
||||||
show_digital_clock((uint8_t)time_info.tm_hour, (uint8_t)time_info.tm_min, main_color_clock);
|
// reset spiral direction to normal drawing LEDs
|
||||||
break;
|
spiral_direction = false;
|
||||||
}
|
// init spiral with new spiral direction
|
||||||
case ST_SPIRAL: // state spiral
|
draw_spiral(true, spiral_direction, MATRIX_WIDTH - 1);
|
||||||
{
|
|
||||||
int res = draw_spiral(false, spiral_direction, MATRIX_WIDTH - 2);
|
|
||||||
if ((bool)res && spiral_direction == 0)
|
|
||||||
{
|
|
||||||
// change spiral direction to closing (draw empty leds)
|
|
||||||
spiral_direction = true;
|
|
||||||
// init spiral with new spiral direction
|
|
||||||
draw_spiral(true, spiral_direction, MATRIX_WIDTH - 1);
|
|
||||||
}
|
|
||||||
else if (res && spiral_direction == 1)
|
|
||||||
{
|
|
||||||
// reset spiral direction to normal drawing leds
|
|
||||||
spiral_direction = false;
|
|
||||||
// init spiral with new spiral direction
|
|
||||||
draw_spiral(true, spiral_direction, MATRIX_WIDTH - 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ST_TETRIS: // state tetris
|
|
||||||
{
|
|
||||||
tetris.loopCycle();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ST_SNAKE: // state snake
|
|
||||||
{
|
|
||||||
snake.loopCycle();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ST_PINGPONG: // state ping pong
|
|
||||||
{
|
|
||||||
pong.loopCycle();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ST_HEARTS:
|
|
||||||
{
|
|
||||||
draw_heart_animation();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ST_TETRIS: // state tetris
|
||||||
|
{
|
||||||
|
tetris.loopCycle();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ST_SNAKE: // state snake
|
||||||
|
{
|
||||||
|
snake.loopCycle();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ST_PINGPONG: // state ping pong
|
||||||
|
{
|
||||||
|
pong.loopCycle();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ST_HEARTS:
|
||||||
|
{
|
||||||
|
draw_heart_animation();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,8 +490,8 @@ void check_night_mode()
|
|||||||
*/
|
*/
|
||||||
void ntp_time_update(uint32 max_update_time)
|
void ntp_time_update(uint32 max_update_time)
|
||||||
{
|
{
|
||||||
// NTP time update
|
static int watchdog_counter = NTP_WATCHDOG_COUNTER_INIT; // Watchdog counter to trigger restart if NTP update was not possible 30 times in a row (5min)
|
||||||
bool ntp_retval = get_ntp_time(max_update_time);
|
bool ntp_retval = get_ntp_time(max_update_time); // NTP time update
|
||||||
|
|
||||||
if (ntp_retval == true)
|
if (ntp_retval == true)
|
||||||
{
|
{
|
||||||
@@ -491,34 +525,34 @@ void on_state_entry(uint8_t state)
|
|||||||
filter_factor = DEFAULT_SMOOTHING_FACTOR;
|
filter_factor = DEFAULT_SMOOTHING_FACTOR;
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case ST_SPIRAL:
|
case ST_SPIRAL:
|
||||||
{
|
{
|
||||||
spiral_direction = 0; // Init spiral with normal drawing mode
|
spiral_direction = 0; // Init spiral with normal drawing mode
|
||||||
draw_spiral(true, spiral_direction, MATRIX_WIDTH - 1);
|
draw_spiral(true, spiral_direction, MATRIX_WIDTH - 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ST_TETRIS:
|
case ST_TETRIS:
|
||||||
{
|
{
|
||||||
filter_factor = 1.0f; // no smoothing
|
filter_factor = 1.0f; // no smoothing
|
||||||
tetris.ctrlStart();
|
tetris.ctrlStart();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ST_SNAKE:
|
case ST_SNAKE:
|
||||||
{
|
{
|
||||||
filter_factor = 1.0f; // no smoothing
|
filter_factor = 1.0f; // no smoothing
|
||||||
snake.initGame();
|
snake.initGame();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ST_PINGPONG:
|
case ST_PINGPONG:
|
||||||
{
|
{
|
||||||
filter_factor = 1.0f; // no smoothing
|
filter_factor = 1.0f; // no smoothing
|
||||||
pong.initGame(1);
|
pong.initGame(1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,10 +600,9 @@ void handle_led_direct()
|
|||||||
// base64 decoding
|
// base64 decoding
|
||||||
char base64data[dataLength];
|
char base64data[dataLength];
|
||||||
data.toCharArray(base64data, dataLength);
|
data.toCharArray(base64data, dataLength);
|
||||||
int base64dataLen = (int)dataLength;
|
unsigned int decodedLength = decode_base64_length((unsigned char *)base64data, dataLength);
|
||||||
int decodedLength = Base64.decodedLength(base64data, base64dataLen);
|
unsigned char byteArray[decodedLength];
|
||||||
char byteArray[decodedLength];
|
decode_base64((unsigned char *)base64data, dataLength, byteArray);
|
||||||
Base64.decode(byteArray, base64data, base64dataLen);
|
|
||||||
|
|
||||||
for (unsigned int i = 0; i < dataLength; i += 4)
|
for (unsigned int i = 0; i < dataLength; i += 4)
|
||||||
{
|
{
|
||||||
@@ -645,11 +678,11 @@ void handle_button()
|
|||||||
void set_main_color(uint8_t red, uint8_t green, uint8_t blue)
|
void set_main_color(uint8_t red, uint8_t green, uint8_t blue)
|
||||||
{
|
{
|
||||||
main_color_clock = LEDMatrix::color_24bit(red, green, blue);
|
main_color_clock = LEDMatrix::color_24bit(red, green, blue);
|
||||||
|
|
||||||
|
// Update colors and save color settings to EEPROM
|
||||||
colors_ps->blue = blue;
|
colors_ps->blue = blue;
|
||||||
colors_ps->red = red;
|
colors_ps->red = red;
|
||||||
colors_ps->green = green;
|
colors_ps->green = green;
|
||||||
|
|
||||||
// save color settings to EEPROM
|
|
||||||
write_settings_to_EEPROM();
|
write_settings_to_EEPROM();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright (C) 2016 Arturo Guadalupi. All right reserved.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include "base64_wrapper.h"
|
|
||||||
#if (defined(__AVR__))
|
|
||||||
#include <avr/pgmspace.h>
|
|
||||||
#else
|
|
||||||
#include <pgmspace.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const char PROGMEM _Base64AlphabetTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
||||||
"abcdefghijklmnopqrstuvwxyz"
|
|
||||||
"0123456789+/";
|
|
||||||
|
|
||||||
int Base64Class::encode(char *output, char *input, int inputLength) {
|
|
||||||
int i = 0, j = 0;
|
|
||||||
int encodedLength = 0;
|
|
||||||
unsigned char A3[3];
|
|
||||||
unsigned char A4[4];
|
|
||||||
|
|
||||||
while(inputLength--) {
|
|
||||||
A3[i++] = *(input++);
|
|
||||||
if(i == 3) {
|
|
||||||
fromA3ToA4(A4, A3);
|
|
||||||
|
|
||||||
for(i = 0; i < 4; i++) {
|
|
||||||
output[encodedLength++] = pgm_read_byte(&_Base64AlphabetTable[A4[i]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(i) {
|
|
||||||
for(j = i; j < 3; j++) {
|
|
||||||
A3[j] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
fromA3ToA4(A4, A3);
|
|
||||||
|
|
||||||
for(j = 0; j < i + 1; j++) {
|
|
||||||
output[encodedLength++] = pgm_read_byte(&_Base64AlphabetTable[A4[j]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
while((i++ < 3)) {
|
|
||||||
output[encodedLength++] = '=';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output[encodedLength] = '\0';
|
|
||||||
return encodedLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Base64Class::decode(char * output, char * input, int inputLength) {
|
|
||||||
int i = 0, j = 0;
|
|
||||||
int decodedLength = 0;
|
|
||||||
unsigned char A3[3];
|
|
||||||
unsigned char A4[4];
|
|
||||||
|
|
||||||
|
|
||||||
while (inputLength--) {
|
|
||||||
if(*input == '=') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
A4[i++] = *(input++);
|
|
||||||
if (i == 4) {
|
|
||||||
for (i = 0; i <4; i++) {
|
|
||||||
A4[i] = lookupTable(A4[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fromA4ToA3(A3,A4);
|
|
||||||
|
|
||||||
for (i = 0; i < 3; i++) {
|
|
||||||
output[decodedLength++] = A3[i];
|
|
||||||
}
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i) {
|
|
||||||
for (j = i; j < 4; j++) {
|
|
||||||
A4[j] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
for (j = 0; j <4; j++) {
|
|
||||||
A4[j] = lookupTable(A4[j]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fromA4ToA3(A3,A4);
|
|
||||||
|
|
||||||
for (j = 0; j < i - 1; j++) {
|
|
||||||
output[decodedLength++] = A3[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output[decodedLength] = '\0';
|
|
||||||
return decodedLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Base64Class::encodedLength(int plainLength) {
|
|
||||||
int n = plainLength;
|
|
||||||
return (n + 2 - ((n + 2) % 3)) / 3 * 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Base64Class::decodedLength(char * input, int inputLength) {
|
|
||||||
int i = 0;
|
|
||||||
int numEq = 0;
|
|
||||||
for(i = inputLength - 1; input[i] == '='; i--) {
|
|
||||||
numEq++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ((6 * inputLength) / 8) - numEq;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Private utility functions
|
|
||||||
inline void Base64Class::fromA3ToA4(unsigned char * A4, unsigned char * A3) {
|
|
||||||
A4[0] = (A3[0] & 0xfc) >> 2;
|
|
||||||
A4[1] = ((A3[0] & 0x03) << 4) + ((A3[1] & 0xf0) >> 4);
|
|
||||||
A4[2] = ((A3[1] & 0x0f) << 2) + ((A3[2] & 0xc0) >> 6);
|
|
||||||
A4[3] = (A3[2] & 0x3f);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void Base64Class::fromA4ToA3(unsigned char * A3, unsigned char * A4) {
|
|
||||||
A3[0] = (A4[0] << 2) + ((A4[1] & 0x30) >> 4);
|
|
||||||
A3[1] = ((A4[1] & 0xf) << 4) + ((A4[2] & 0x3c) >> 2);
|
|
||||||
A3[2] = ((A4[2] & 0x3) << 6) + A4[3];
|
|
||||||
}
|
|
||||||
|
|
||||||
inline unsigned char Base64Class::lookupTable(char c) {
|
|
||||||
if(c >='A' && c <='Z') return c - 'A';
|
|
||||||
if(c >='a' && c <='z') return c - 71;
|
|
||||||
if(c >='0' && c <='9') return c + 4;
|
|
||||||
if(c == '+') return 62;
|
|
||||||
if(c == '/') return 63;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Base64Class Base64;
|
|
||||||
Reference in New Issue
Block a user