/* ; Project: Open Vehicle Monitor System ; Date: 14th March 2017 ; ; Changes: ; 1.0 Initial release ; ; (C) 2011 Michael Stegen / Stegen Electronics ; (C) 2011-2017 Mark Webb-Johnson ; (C) 2011 Sonny Chen @ EPRO/DX ; (C) 2018 Michael Balzer ; ; Permission is hereby granted, free of charge, to any person obtaining a copy ; of this software and associated documentation files (the "Software"), to deal ; in the Software without restriction, including without limitation the rights ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ; copies of the Software, and to permit persons to whom the Software is ; furnished to do so, subject to the following conditions: ; ; The above copyright notice and this permission notice shall be included in ; all copies or substantial portions of the Software. ; ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ; THE SOFTWARE. */ #ifndef __WEBSERVER_H__ #define __WEBSERVER_H__ #include #include #include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/timers.h" #include "ovms_events.h" #include "ovms_metrics.h" #include "ovms_config.h" #include "ovms_notify.h" #include "ovms_command.h" #include "ovms_shell.h" #include "ovms_netmanager.h" #include "ovms_utils.h" #include "log_buffers.h" // The setup wizard currently is tailored to be used with a WiFi enabled module: #ifdef CONFIG_OVMS_COMP_WIFI #define WEBSRV_HAVE_SETUPWIZARD 1 #endif #define OVMS_GLOBAL_AUTH_FILE "/store/.htpasswd" #define SESSION_COOKIE_NAME "ovms_session" #define SESSION_TTL 3600 #define SESSION_CHECK_INTERVAL 60 #define NUM_SESSIONS 5 #define XFER_CHUNK_SIZE 1024 #define WEBSRV_USE_MG_BROADCAST 0 // Note: mg_broadcast() not working reliably yet, do not enable for production! // Asset URLs with versioning: #define URL_ASSETS_SCRIPT_JS "/assets/script.js?v=" STR(MTIME_ASSETS_SCRIPT_JS) #define URL_ASSETS_CHARTS_JS "/assets/charts.js?v=" STR(MTIME_ASSETS_CHARTS_JS) #define URL_ASSETS_TABLES_JS "/assets/tables.js?v=" STR(MTIME_ASSETS_TABLES_JS) #define URL_ASSETS_STYLE_CSS "/assets/style.css?v=" STR(MTIME_ASSETS_STYLE_CSS) #define URL_ASSETS_FAVICON_PNG "/apple-touch-icon.png?v=" STR(MTIME_ASSETS_FAVICON_PNG) #define URL_ASSETS_ZONES_JSON "/assets/zones.json?v=" STR(MTIME_ASSETS_ZONES_JSON) struct user_session { uint64_t id; time_t last_used; //char* username; }; typedef enum { PageMenu_None, PageMenu_Main, // → main menu PageMenu_Tools, // → tools menu PageMenu_Config, // → config menu PageMenu_Vehicle, // → vehicle menu } PageMenu_t; inline PageMenu_t Code2PageMenu(std::string code) { if (code == "Main") return PageMenu_Main; else if (code == "Tools") return PageMenu_Tools; else if (code == "Config") return PageMenu_Config; else if (code == "Vehicle") return PageMenu_Vehicle; else return PageMenu_None; } typedef enum { PageAuth_None, // public PageAuth_Cookie, // use auth cookie PageAuth_File, // use htaccess file(s) (digest auth) } PageAuth_t; inline PageAuth_t Code2PageAuth(std::string code) { if (code == "Cookie") return PageAuth_Cookie; else if (code == "File") return PageAuth_File; else return PageAuth_None; } typedef enum { PageResult_OK, // NOP PageResult_STOP, // stop page callbacks & handler execution } PageResult_t; struct PageEntry; typedef struct PageEntry PageEntry_t; struct PageContext; typedef struct PageContext PageContext_t; typedef void (*PageHandler_t)(PageEntry_t& p, PageContext_t& c); typedef PageResult_t (*PageCallback_t)(PageEntry_t& p, PageContext_t& c, const std::string& hook); // Page hook point definition (to be used in a handler with p = PageEntry&, c = PageContext&): #define PAGE_HOOK(code) { if (p.callback(c, code) == PageResult_STOP) return; } /** * PageContext: execution context of a URI/page handler call providing * access to the HTTP context and utilities to generate HTML output. */ struct PageContext : public ExternalRamAllocated { mg_connection *nc; http_message *hm; user_session *session; std::string method; std::string uri; // utils: std::string getvar(const std::string& name, size_t maxlen=200); bool getvar(const std::string& name, extram::string& dst); static std::string encode_html(const char* text); static std::string encode_html(const std::string text); static extram::string encode_html(const extram::string& text); static std::string make_id(const char* text); static std::string make_id(const std::string text); // output: void error(int code, const char* text); void head(int code, const char* headers=NULL); void print(const std::string text); void print(const extram::string text); void print(const char* text); void printf(const char *fmt, ...); void done(); void panel_start(const char* type, const char* title); void panel_end(const char* footer=""); void form_start(std::string action, const char* target=NULL); void form_end(); void fieldset_start(const char* title, const char* css_class=NULL); void fieldset_end(); void hr(); void input(const char* type, const char* label, const char* name, const char* value, const char* placeholder=NULL, const char* helptext=NULL, const char* moreattrs=NULL, const char* unit=NULL); void input_text(const char* label, const char* name, const char* value, const char* placeholder=NULL, const char* helptext=NULL, const char* moreattrs=NULL); void input_password(const char* label, const char* name, const char* value, const char* placeholder=NULL, const char* helptext=NULL, const char* moreattrs=NULL); void input_select_start(const char* label, const char* name); void input_select_option(const char* label, const char* value, bool selected); void input_select_end(const char* helptext=NULL); void input_radio_start(const char* label, const char* name); void input_radio_option(const char* name, const char* label, const char* value, bool selected); void input_radio_end(const char* helptext=NULL); void input_radiobtn_start(const char* label, const char* name); void input_radiobtn_option(const char* name, const char* label, const char* value, bool selected); void input_radiobtn_end(const char* helptext=NULL); void input_checkbox(const char* label, const char* name, bool value, const char* helptext=NULL); void input_slider(const char* label, const char* name, int size, const char* unit, int enabled, double value, double defval, double min, double max, double step=1, const char* helptext=NULL); void input_button(const char* btnclass, const char* label, const char* name=NULL, const char* value=NULL); void input_info(const char* label, const char* text); void alert(const char* type, const char* text); PageResult_t callback(PageEntry_t& p, const std::string& hook); }; /** * PageEntry: HTTP URI page handler entry for OvmsWebServer. * * Created by OvmsWebServer::RegisterPage(). The registered handler will be called * with both PageEntry and PageContext, so one handler can serve multiple * URIs or patterns. */ struct PageCallbackEntry { std::string caller; PageCallback_t handler; PageCallbackEntry(std::string _caller, PageCallback_t _handler) { caller = _caller; handler = _handler; } }; typedef std::forward_list PageCallbackMap_t; struct PageEntry { std::string uri; std::string label; PageHandler_t handler; PageMenu_t menu; PageAuth_t auth; PageCallbackMap_t callbacklist; PageEntry(std::string _uri, std::string _label, PageHandler_t _handler, PageMenu_t _menu=PageMenu_None, PageAuth_t _auth=PageAuth_None) { uri = _uri; label = _label; handler = _handler; menu = _menu; auth = _auth; } void Serve(PageContext_t& c); void RegisterCallback(std::string caller, PageCallback_t handler, int priority=0); void DeregisterCallback(std::string caller); PageResult_t callback(PageContext_t& c, const std::string& hook); }; typedef std::forward_list PageMap_t; /** * Plugin Cache */ struct PagePluginContent { bool m_pluginstore; std::string m_path; extram::string m_content; PagePluginContent(std::string path, bool pluginstore=false) { m_path = path; m_pluginstore = pluginstore; } extram::string& GetContent() { if (m_content.empty()) LoadContent(); return m_content; } void LoadContent(); }; typedef std::map PagePluginMap; typedef std::multimap PagePluginMultiMap; /** * MgHandler is the base mongoose connection handler interface class * to implement stateful connections. * * An MgHandler instance automatically attaches itself to (and detaches from) * the mg_connection via the user_data field. * The HandleEvent() method will be called prior to the framework handler. */ class MgHandler : public ExternalRamAllocated { public: MgHandler(mg_connection* nc) { m_nc = nc; if (m_nc) m_nc->user_data = this; } virtual ~MgHandler() { if (m_nc) m_nc->user_data = NULL; } public: virtual int HandleEvent(int ev, void* p) = 0; void RequestPoll(); static void HandlePoll(mg_connection* nc, int ev, void* p); public: mg_connection* m_nc = NULL; }; /** * HttpDataSender transmits a memory area (ROM/RAM) in HTTP chunks of XFER_CHUNK_SIZE size. */ typedef std::pair MemRegion; typedef std::vector MemRegionList; class HttpDataSender : public MgHandler { public: HttpDataSender(mg_connection* nc, MemRegionList xferlist, bool keepalive=true); HttpDataSender(mg_connection* nc, const uint8_t* data, size_t size, bool keepalive=true); ~HttpDataSender(); public: int HandleEvent(int ev, void* p); public: MemRegionList m_xferlist; // list of memory regions to send ssize_t m_xfer = 0; // current region in process const uint8_t* m_data = NULL; // pointer to data size_t m_size = 0; // size of data to send size_t m_sent = 0; // size sent up to now bool m_keepalive = false; // false = close connection when done }; /** * HttpStringSender transmits a std::string in HTTP chunks of XFER_CHUNK_SIZE size. * Note: the string is deleted after transmission. */ class HttpStringSender : public MgHandler { public: HttpStringSender(mg_connection* nc, std::string* msg, bool keepalive=true); ~HttpStringSender(); public: int HandleEvent(int ev, void* p); public: std::string* m_msg = NULL; // pointer to data size_t m_sent = 0; // size sent up to now bool m_keepalive = false; // false = close connection when done }; /** * WebSocketHandler transmits JSON data in chunks to the WebSocket client * and coordinates transmits initiated from other contexts (i.e. events). * * On creation it will do a full update of all metrics. * Later on, it receives TX jobs through the queue. */ enum WebSocketTxJobType { WSTX_None = 0, WSTX_Event, // payload: event WSTX_MetricsAll, // payload: - WSTX_MetricsUpdate, // payload: - WSTX_Config, // payload: config (todo) WSTX_Notify, // payload: notification WSTX_LogBuffers, // payload: logbuffers }; struct WebSocketTxJob { WebSocketTxJobType type; union { char* event; OvmsConfigParam* config; OvmsNotifyEntry* notification; LogBuffers* logbuffers; }; void clear(size_t client); }; struct WebSocketTxTodo { int client; WebSocketTxJob job; }; class WebSocketHandler : public MgHandler, public OvmsWriter { public: WebSocketHandler(mg_connection* nc, size_t slot, size_t modifier, size_t reader); ~WebSocketHandler(); public: bool Lock(TickType_t xTicksToWait); void Unlock(); bool AddTxJob(WebSocketTxJob job, bool init_tx=true); void ClearTxJob(WebSocketTxJob &job); bool GetNextTxJob(); void InitTx(); void ContinueTx(); void ProcessTxJob(); int HandleEvent(int ev, void* p); void HandleIncomingMsg(std::string msg); public: void Subscribe(std::string topic); void Unsubscribe(std::string topic); bool IsSubscribedTo(std::string topic); // OvmsWriter: public: void Log(LogBuffers* message); public: size_t m_slot = 0; size_t m_modifier = 0; // "our" metrics modifier size_t m_reader = 0; // "our" notification reader id QueueHandle_t m_jobqueue = NULL; uint32_t m_jobqueue_overflow_status = 0; uint32_t m_jobqueue_overflow_logged = 0; uint32_t m_jobqueue_overflow_dropcnt = 0; uint32_t m_jobqueue_overflow_dropcntref = 0; WebSocketTxJob m_job = {}; int m_sent = 0; int m_ack = 0; std::set m_subscriptions; }; struct WebSocketSlot { WebSocketHandler* handler; size_t modifier; size_t reader; }; typedef std::vector WebSocketSlots; /** * HttpCommandStream: execute command, stream output to HTTP connection */ class HttpCommandStream : public OvmsShell, public MgHandler { public: HttpCommandStream(mg_connection* nc, extram::string command, bool javascript=false, int verbosity=COMMAND_RESULT_VERBOSE); ~HttpCommandStream(); public: void ProcessQueue(); int HandleEvent(int ev, void* p); static void CommandTask(void* object); public: extram::string m_command; bool m_javascript = false; TaskHandle_t m_cmdtask = NULL; QueueHandle_t m_writequeue = NULL; bool m_done = false; size_t m_sent = 0; int m_ack = 0; public: void Initialize(bool print); virtual bool IsInteractive() { return false; } int puts(const char* s); int printf(const char* fmt, ...); ssize_t write(const void *buf, size_t nbyte); void Log(LogBuffers* message); }; /** * OvmsWebServer: main web framework (static instance: MyWebServer) * * Register custom page handlers through the RegisterPage() API. */ class OvmsWebServer : public ExternalRamAllocated { public: OvmsWebServer(); ~OvmsWebServer(); public: static void EventHandler(mg_connection *nc, int ev, void *p); void NetManInit(std::string event, void* data); void NetManStop(std::string event, void* data); void ConfigChanged(std::string event, void* data); void UpdateGlobalAuthFile(); static const std::string MakeDigestAuth(const char* realm, const char* username, const char* password); static const std::string ExecuteCommand(const std::string command, int verbosity=COMMAND_RESULT_NORMAL); void EventListener(std::string event, void* data); static void UpdateTicker(TimerHandle_t timer); static bool NotificationFilter(int client, OvmsNotifyType* type, const char* subtype); static bool IncomingNotification(int client, OvmsNotifyType* type, OvmsNotifyEntry* entry); public: void RegisterPage(std::string uri, std::string label, PageHandler_t handler, PageMenu_t menu=PageMenu_None, PageAuth_t auth=PageAuth_None, int priority=0); void DeregisterPage(std::string uri); PageEntry* FindPage(std::string uri); bool RegisterCallback(std::string caller, std::string uri, PageCallback_t handler, int priority=0); void DeregisterCallbacks(std::string caller); void RegisterPlugins(); void DeregisterPlugins(); void ReloadPlugin(std::string path); static void PluginHandler(PageEntry_t& p, PageContext_t& c); static PageResult_t PluginCallback(PageEntry_t& p, PageContext_t& c, const std::string& hook); public: user_session* CreateSession(const http_message *hm); void DestroySession(user_session *s); user_session* GetSession(http_message *hm); void CheckSessions(void); static bool CheckLogin(std::string username, std::string password); public: WebSocketHandler* CreateWebSocketHandler(mg_connection* nc); void DestroyWebSocketHandler(WebSocketHandler* handler); bool AddToBacklog(int client, WebSocketTxJob job); public: static std::string CreateMenu(PageContext_t& c); static void OutputHome(PageEntry_t& p, PageContext_t& c); static void HandleRoot(PageEntry_t& p, PageContext_t& c); static void HandleAsset(PageEntry_t& p, PageContext_t& c); static void HandleMenu(PageEntry_t& p, PageContext_t& c); static void HandleHome(PageEntry_t& p, PageContext_t& c); static void HandleLogin(PageEntry_t& p, PageContext_t& c); static void HandleLogout(PageEntry_t& p, PageContext_t& c); static void OutputReboot(PageEntry_t& p, PageContext_t& c); static void OutputReconnect(PageEntry_t& p, PageContext_t& c, const char* info=NULL); public: static void HandleStatus(PageEntry_t& p, PageContext_t& c); static void HandleCommand(PageEntry_t& p, PageContext_t& c); static void HandleFile(PageEntry_t& p, PageContext_t& c); static void HandleShell(PageEntry_t& p, PageContext_t& c); static void HandleDashboard(PageEntry_t& p, PageContext_t& c); static void HandleBmsCellMonitor(PageEntry_t& p, PageContext_t& c); static void HandleCfgBrakelight(PageEntry_t& p, PageContext_t& c); static void HandleEditor(PageEntry_t& p, PageContext_t& c); static void HandleCfgPassword(PageEntry_t& p, PageContext_t& c); static void HandleCfgVehicle(PageEntry_t& p, PageContext_t& c); static void HandleCfgModem(PageEntry_t& p, PageContext_t& c); static void HandleCfgServerV2(PageEntry_t& p, PageContext_t& c); static void HandleCfgServerV3(PageEntry_t& p, PageContext_t& c); static void HandleCfgNotifications(PageEntry_t& p, PageContext_t& c); static void HandleCfgWebServer(PageEntry_t& p, PageContext_t& c); #ifdef CONFIG_OVMS_COMP_PUSHOVER static void HandleCfgPushover(PageEntry_t& p, PageContext_t& c); #endif static void HandleCfgWifi(PageEntry_t& p, PageContext_t& c); static void OutputWifiTable(PageEntry_t& p, PageContext_t& c, const std::string prefix, const std::string paramname, const std::string autostart_ssid); static void UpdateWifiTable(PageEntry_t& p, PageContext_t& c, const std::string prefix, const std::string paramname, std::string& warn, std::string& error, int pass_minlen); static void HandleCfgAutoInit(PageEntry_t& p, PageContext_t& c); static void HandleCfgFirmware(PageEntry_t& p, PageContext_t& c); static void HandleCfgLogging(PageEntry_t& p, PageContext_t& c); static void HandleCfgLocations(PageEntry_t& p, PageContext_t& c); static void HandleCfgBackup(PageEntry_t& p, PageContext_t& c); static void HandleCfgPlugins(PageEntry_t& p, PageContext_t& c); #ifdef WEBSRV_HAVE_SETUPWIZARD public: void CfgInitStartup(); static void HandleCfgInit(PageEntry_t& p, PageContext_t& c); static std::string CfgInitSetStep(std::string step, int timeout=0); void CfgInitTicker(); std::string CfgInit1(PageEntry_t& p, PageContext_t& c, std::string step); std::string CfgInit2(PageEntry_t& p, PageContext_t& c, std::string step); std::string CfgInit3(PageEntry_t& p, PageContext_t& c, std::string step); std::string CfgInit4(PageEntry_t& p, PageContext_t& c, std::string step); std::string CfgInit5(PageEntry_t& p, PageContext_t& c, std::string step); #endif // WEBSRV_HAVE_SETUPWIZARD public: bool m_running; bool m_configured; #if MG_ENABLE_FILESYSTEM bool m_file_enable; mg_serve_http_opts m_file_opts; #endif //MG_ENABLE_FILESYSTEM PageMap_t m_pagemap; PagePluginMap m_plugin_pages; PagePluginMultiMap m_plugin_parts; user_session m_sessions[NUM_SESSIONS]; size_t m_client_cnt; // number of active WebSocket clients SemaphoreHandle_t m_client_mutex; WebSocketSlots m_client_slots; QueueHandle_t m_client_backlog; TimerHandle_t m_update_ticker; int m_init_timeout; int m_shutdown_countdown; }; extern OvmsWebServer MyWebServer; /** * DashboardConfig: */ struct DashboardConfig { std::string gaugeset1; }; #endif //#ifndef __WEBSERVER_H__