diff --git a/nano_core/include/web_server.hpp b/nano_core/include/web_server.hpp index 2e98acc..e2ad141 100644 --- a/nano_core/include/web_server.hpp +++ b/nano_core/include/web_server.hpp @@ -1,5 +1,35 @@ #ifndef _WEBSERVER_HPP #define _WEBSERVER_HPP +#include +#include +#include + +#include "speed.hpp" + +static const char WEB_ROOT[] = "/web"; + +class NanoServer { + public: + NanoServer(int port) : server_(port), manager_(nullptr) {} + + bool init(fs::FS& fs, SpeedManager* manager); + + void begin(); + void handle_client(); + + private: + WebServer server_; + SpeedManager* manager_; + + std::function save_func_; + + bool server_dir(fs::FS &fs); + + void handle_status_query(); + void handle_speed_set(); + void handle_config_query(); + void handle_config_set(); +}; #endif // _WEBSERVER_HPP diff --git a/nano_core/src/main.cpp b/nano_core/src/main.cpp index 8c07a45..0d0360b 100644 --- a/nano_core/src/main.cpp +++ b/nano_core/src/main.cpp @@ -1,15 +1,14 @@ #include #include -#include +#include #include "configure.hpp" #include "motor.hpp" #include "speed.hpp" +#include "web_server.hpp" #include "functional" -// WebServer server(80); - #define DIR 18 #define STEP 19 #define S1 34 @@ -19,106 +18,60 @@ SpeedManager manager(S1, S2, S3, S4); Motor motor(DIR, STEP); +NanoServer server(80); -void listDir(fs::FS &fs, const char *dirname, uint8_t levels) { - Serial.printf("Listing directory: %s\r\n", dirname); - - File root = fs.open(dirname); - if (!root) { - Serial.println("- failed to open directory"); - return; - } - if (!root.isDirectory()) { - Serial.println(" - not a directory"); - return; - } - - File file = root.openNextFile(); - while (file) { - if (file.isDirectory()) { - Serial.print(" DIR : "); - Serial.println(file.name()); - if (levels) { - listDir(fs, file.name(), levels - 1); - } - } else { - Serial.print(" FILE: "); - Serial.print(file.name()); - Serial.print("\tSIZE: "); - Serial.println(file.size()); - } - file = root.openNextFile(); - } -} - -// void web_task(void* parameters) { -// while (true) { -// server.handleClient(); -// } -// } - -// void motor_task(void *parameters) { -// while (true) { -// motor.tick(); -// vTaskDelay(1); -// } -// } +const byte DNS_PORT = 53; +IPAddress apIP(192, 168, 1, 1); +DNSServer dnsServer; void manager_task(void *parameters) { while (true) { manager.tick(); - delay(1000); + server.handle_client(); + dnsServer.processNextRequest(); } } void setup() { - Serial.begin(115200); + setCpuFrequencyMhz(240); + // Serial.begin(115200); if (!SPIFFS.begin(true)) { - Serial.println("SPIFFS Mount Failed"); + // Serial.println("SPIFFS Mount Failed"); return; } - listDir(SPIFFS, "/", 0); - if (!Config::inst().load(SPIFFS)) { - Serial.println("load failed"); + // Serial.println("load failed"); + return; } - char buffer[128]; - sprintf(buffer, "%s, %s, %lf", Config::inst().data.ssid, - Config::inst().data.password, Config::inst().data.gearbox_ratio); - Serial.println(buffer); + WiFi.mode(WIFI_AP); + WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); + WiFi.softAP(Config::inst().data.ssid, Config::inst().data.password, 13, 0, + 1); manager.register_delay_change([](uint32_t delay) { - Serial.println("delay"); - Serial.println(delay); motor.on_delay_change(delay); }); manager.register_dir_change([](Direction dir) { - Serial.println("dir"); - Serial.println(dir); motor.on_dir_change(dir); }); - xTaskCreatePinnedToCore(manager_task, "manager_task", 4096, nullptr, 1, nullptr, - 0); - - // WiFi.softAP("nanolight", "12345678", 13, 0, 1); + if (!server.init(SPIFFS, &manager)) { + // Serial.println("server init failed"); + return; + } - // server.serveStatic("/", SPIFFS, "/index.html"); - // server.serveStatic("/main.58843f1a.js", SPIFFS, "/main.58843f1a.js"); - // server.serveStatic("/main.f7a8434c.css", SPIFFS, "/main.f7a8434c.css"); - // server.serveStatic("/asset-manifest.json", SPIFFS, - // "/asset-manifest.json"); + dnsServer.setTTL(300); + dnsServer.setErrorReplyCode(DNSReplyCode::ServerFailure); - // server.begin(); + xTaskCreatePinnedToCore(manager_task, "manager_task", 4096, nullptr, 1, + nullptr, 0); - // xTaskCreatePinnedToCore(web_task, "web_task", 10000, nullptr, 1, nullptr, - // 0); + server.begin(); + dnsServer.start(DNS_PORT, "www.nanolight.cn", apIP); } -void loop() { - motor.tick(); -} +void loop() { motor.tick(); } diff --git a/nano_core/src/speed.cpp b/nano_core/src/speed.cpp index 7fd6326..e7c0765 100644 --- a/nano_core/src/speed.cpp +++ b/nano_core/src/speed.cpp @@ -6,9 +6,10 @@ #include "configure.hpp" bool Speed::from_json(const char* str, int len) { - StaticJsonDocument doc; + StaticJsonDocument<128> doc; auto err = deserializeJson(doc, str); if (err) { + Serial.println(err.c_str()); return false; } int tmp = doc["d"]; @@ -101,9 +102,7 @@ Speed SpeedManager::get_speed(int id) { return Speed(dir, SpeedType::FACTOR, speed_tab[i]); } -Direction neg(Direction d) { - return Direction(1 - uint32_t(d)); -} +Direction neg(Direction d) { return Direction(1 - uint32_t(d)); } Speed SpeedManager::max_speed(Direction dir) { double stepper_step = @@ -152,8 +151,7 @@ void SpeedManager::tick() { new_dir = neg(new_dir); stepper_step = -stepper_step; } - new_delay = - uint32_t(stepper_step * 1000000 / new_speed.to_degree()); + new_delay = uint32_t(stepper_step * 1000000 / new_speed.to_degree()); if (new_delay < MOTOR_MIN_DELAY_US) { new_delay = MOTOR_MIN_DELAY_US; new_speed = max_speed(new_speed.d); @@ -166,6 +164,8 @@ void SpeedManager::tick() { motor_delay_ = new_delay; if (new_dir != Direction::STOP) { status_.start_time_ms = millis(); + } else { + status_.start_time_ms = 0; } if (nullptr != dir_callback_) { dir_callback_(motor_dir_); diff --git a/nano_core/src/web_server.cpp b/nano_core/src/web_server.cpp index 740dd70..a928ed2 100644 --- a/nano_core/src/web_server.cpp +++ b/nano_core/src/web_server.cpp @@ -1,2 +1,89 @@ #include "web_server.hpp" +#include "configure.hpp" + +bool NanoServer::init(fs::FS& fs, SpeedManager* manager) { + manager_ = manager; + save_func_ = [&fs]() { + return Config::inst().save(fs); + }; + if (!server_dir(fs)) { + Serial.println("failed to server static"); + return false; + } + server_.on("/status", HTTP_POST, [this]() { this->handle_status_query(); }); + server_.on("/set_status", HTTP_POST, + [this]() { this->handle_speed_set(); }); + server_.on("/config", HTTP_POST, [this]() { this->handle_config_query(); }); + server_.on("/set_config", HTTP_POST, + [this]() { this->handle_config_set(); }); + return true; +} + +void NanoServer::begin() { server_.begin(); } + +void NanoServer::handle_client() { server_.handleClient(); } + +bool NanoServer::server_dir(fs::FS& fs) { + File root = fs.open(WEB_ROOT); + if (!root) { + Serial.println("failed to open www"); + return false; + } + if (!root.isDirectory()) { + Serial.println(" - not a directory"); + return false; + } + File file = root.openNextFile(); + auto folder_len = strlen(WEB_ROOT); + server_.serveStatic("/", fs, "/web/index.html"); + while (file) { + if (!file.isDirectory()) { + auto filename = file.name(); + server_.serveStatic(filename + folder_len, fs, filename); + } + file = root.openNextFile(); + } + return true; +} + +void NanoServer::handle_status_query() { + char buf[STATUS_JSON_STR_LEN]; + int len; + if (!manager_->get_json_status(buf, STATUS_JSON_STR_LEN, &len)) { + server_.send(500, "text/json", R"({"ret": false})"); + } + server_.send(200, "text/json", buf); +} + +void NanoServer::handle_speed_set() { + String arg = server_.arg(0); + if (!manager_->set_web_json_speed(arg.c_str(), arg.length())) { + server_.send(500, "text/json", R"({"ret": false})"); + } + manager_->need_update(); + handle_status_query(); +} + +void NanoServer::handle_config_query() { + char buf[CONFIG_STR_LEN]; + int len; + if (!Config::inst().data.dump_json(buf, CONFIG_STR_LEN, &len)) { + server_.send(500, "text/json", R"({"ret": false})"); + } + server_.send(200, "text/json", buf); +} + +void NanoServer::handle_config_set() { + String arg = server_.arg(0); + if (!Config::inst().data.load_json(arg.c_str(), arg.length())) { + server_.send(500, "text/json", R"({"ret": false})"); + } + if (save_func_) { + if (!save_func_()) { + server_.send(500, "text/json", R"({"ret": false})"); + } + } + manager_->need_update(); + handle_config_query(); +} diff --git a/nano_view/src/App.js b/nano_view/src/App.js index 3491016..1ada913 100644 --- a/nano_view/src/App.js +++ b/nano_view/src/App.js @@ -140,6 +140,7 @@ class App extends React.Component { // user input input_speed: this.default_speed_list[0], input_direction: this.default_direction_list[0], + btn_touched: false, // config info config_fetched: false, config_ssid: "", @@ -154,7 +155,89 @@ class App extends React.Component { }; } + componentDidMount() { + setInterval(this.fetch_status.bind(this), 1000); + this.fetch_config(); + } + ///////> network handler + fetch_status() { + fetch("/status", { + method: 'POST' + }).then(function (response) { + if (response.ok) { + return response.json(); + } + throw new Error('error'); + }).then(data => this.update_status(data)).catch(err => this.failed_handle(err)); + } + + fetch_config() { + fetch("/config", { + method: 'POST' + }).then(function (response) { + if (response.ok) { + return response.json(); + } + throw new Error('error'); + }).then(data => this.update_config(data)).catch(err => { + setTimeout(this.fetch_config.bind(this), 1000); + }); + } + + set_speed(dir, speed) { + return fetch("/set_status", { + method: "POST", + body: JSON.stringify({ + d: dir, + t: speed.type, + s: speed.speed + }) + }).then(function (response) { + if (response.ok) { + return response.json(); + } + throw new Error('error'); + }).then(data => this.update_status(data)); + } + + set_config() { + fetch("/set_config", { + method: "POST", + body: JSON.stringify({ + ssid: this.state.config_ssid_user, + pwd: this.state.config_pwd_user, + ratio: this.state.config_ratio_user + }) + }).then(function (response) { + if (response.ok) { + return response.json(); + } + throw new Error('error'); + }).then(data => this.update_config(data)).catch(err => {}); + } + + update_status(response_data) { + this.setState({ + connection: true, + status_duration: response_data['t'], + status_direction: response_data['s']['d'], + status_speed: new Speed(response_data['s']['t'], response_data['s']['s']) + }) + } + + update_config(response_data) { + this.setState({ + config_fetched: true, + config_ssid: response_data['ssid'], + config_pwd: response_data['pwd'], + config_ratio: response_data['ratio'] + }); + } + + failed_handle(err) { + this.setState({connection: false, config_fetched: false}) + } ///////> element callback trigger_visual_mode() { @@ -222,19 +305,32 @@ class App extends React.Component { } handle_start_btn(event) { - console.log("start"); + if (this.state.status_direction === DIRECTION.STOP) { + this.set_speed(this.state.input_direction, this.state.input_speed).catch(err => this.failed_handle(err)); + } else { + this.set_speed(DIRECTION.STOP, new Speed(0, 0)).catch(err => this.failed_handle(err)); + } } handle_btn_push(event) { - console.log("btn down"); + if (this.state.status_direction !== DIRECTION.STOP) { + return; + } + this.setState({btn_touched: true}) + this.set_speed(this.state.input_direction, this.state.input_speed).catch(err => { + }); } handle_btn_release(event) { - console.log("btn up"); + if (this.state.btn_touched) { + this.set_speed(DIRECTION.STOP, new Speed(0, 0)).catch(err => { + }); + this.setState({btn_touched: false}) + } } handle_config_save(event) { - console.log("save config"); + this.set_config(); } render() { @@ -244,9 +340,9 @@ class App extends React.Component { + onClick={this.trigger_dir_mode.bind(this)}>{DIR_MODE.str(DIR_MODE.not(this.state.dir_mode))} + onClick={this.trigger_visual_mode.bind(this)}>{VISUAL_MODE.str(VISUAL_MODE.not(this.state.visual_mode))}
@@ -260,9 +356,9 @@ class App extends React.Component {
{DIR_MODE.str(this.state.dir_mode)}模式

{ - this.state.dir_mode === DIR_MODE.SKY ? - DIRECTION.str_clock(this.state.status_direction) : - DIRECTION.str(this.state.status_direction)}

+ this.state.dir_mode === DIR_MODE.SKY ? + DIRECTION.str(this.state.status_direction) : + DIRECTION.str_clock(this.state.status_direction)}

@@ -327,7 +423,7 @@ class App extends React.Component { { - this.state.dir_mode === DIR_MODE.SKY ? + this.state.dir_mode === DIR_MODE.SKY ? DIRECTION.str(d) : DIRECTION.str_clock(d)} ))} @@ -338,7 +434,7 @@ class App extends React.Component {
-