Add uart-bridge example

This commit is contained in:
Sergey Lyubka committed 2022-06-08 08:58:32 +01:00
1 parent c701315ab5
commit 2e750236be
10 files changed
+2098

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
PROG ?= example
SOURCES ?= ../../mongoose.c main.c net.c packed_fs.c
CFLAGS ?= -I../.. -DMG_ENABLE_PACKED_FS=1 -DMG_ENABLE_LINES=1 $(EXTRA_CFLAGS)
FILES_TO_EMBED ?= $(wildcard web_root/*)
ROOT ?= $(realpath $(CURDIR)/../../..)
DOCKER ?= docker run --rm -e Tmp=. -e WINEDEBUG=-all -v $(ROOT):$(ROOT) -w $(CURDIR)
VC98 ?= $(DOCKER) mdashnet/vc98 wine
MINGW ?= $(DOCKER) mdashnet/mingw
all: $(PROG)
$(RUN) ./$(PROG)
$(PROG): $(SOURCES) $(FILES_TO_EMBED)
$(CC) ../../test/pack.c -o pack
./pack $(FILES_TO_EMBED) > packed_fs.c
$(CC) -W -Wall -Wextra -O0 -g3 $(CFLAGS) -o $(PROG) $(SOURCES)
clean:
rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb log.txt pack
+16
View File
@@ -0,0 +1,16 @@
# A UART to network bridge
This example is a demonstration of how Mongoose Library could be integrated
into an embedded device and provide a UART-to-Network bridge capability:
- A device opens listening TCP port and Websocket port and waits for connections
- When a client connects, data is exchanged with the device's UART
- Everything that client send, is sent to the UART
- Everything that is read from the UART, gets sent to the client
- Multiple clients are allowed
- Live UART console allows to talk to the UART from the web page
- Web UI is hardcoded into the binary and does not need a filesystem
# Screenshots
![](screenshots/dashboard.png)
+18
View File
@@ -0,0 +1,18 @@
// Copyright (c) 2022 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
const char *s_listening_url = "http://0.0.0.0:8000";
void uart_bridge_fn(struct mg_connection *, int, void *, void *);
int main(void) {
struct mg_mgr mgr;
mg_log_set("2"); // Set to 3 for debug, to 4 for very verbose level
mg_mgr_init(&mgr);
mg_http_listen(&mgr, s_listening_url, uart_bridge_fn, &mgr);
MG_INFO(("Listening on %s", s_listening_url));
while (mgr.conns != NULL) mg_mgr_poll(&mgr, 500);
mg_mgr_free(&mgr);
return 0;
}
+110
View File
@@ -0,0 +1,110 @@
// Copyright (c) 2022 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
static const char *s_tcp_url = "tcp://0.0.0.0:4001";
static const char *s_ws_url = "ws://0.0.0.0:4002";
static struct mg_connection *s_tcp_conn;
static struct mg_connection *s_ws_conn;
static int s_rx = 4;
static int s_tx = 5;
static int s_baud = 115200;
// Let users define their own UART API. If they don't, use a dummy one
#if defined(UART_API_IMPLEMENTED)
#else
void uart_init(int tx, int rx, int baud) {
// We use stdin/stdout as UART. Make stdin non-blocking
fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
(void) tx, (void) rx, (void) baud;
}
void uart_write(const void *buf, size_t len) {
fwrite(buf, 1, len, stdout); // Write to stdout
fflush(stdout);
}
int uart_read(char *buf, size_t len) {
return read(0, buf, len); // Read from stdin
}
#endif
// Event handler for a connected Websocket client
static void ws_fn(struct mg_connection *c, int ev, void *evd, void *fnd) {
if (ev == MG_EV_HTTP_MSG) {
mg_ws_upgrade(c, evd, NULL);
} else if (ev == MG_EV_WS_OPEN) {
// c->is_hexdumping = 1;
c->label[0] = 'W'; // When WS handhake is done, mark us as WS client
} else if (ev == MG_EV_WS_MSG) {
struct mg_ws_message *wm = (struct mg_ws_message *) evd;
uart_write(wm->data.ptr, wm->data.len); // Send to UART
c->recv.len = 0; // Discard received data
} else if (ev == MG_EV_CLOSE) {
if (c->is_listening) s_ws_conn = NULL;
}
(void) fnd;
}
// Event handler for a connected TCP client
static void tcp_fn(struct mg_connection *c, int ev, void *evd, void *fnd) {
if (ev == MG_EV_OPEN) {
// c->is_hexdumping = 1;
c->label[0] = 'T'; // When client is connected, mark us as TCP client
} else if (ev == MG_EV_READ) {
uart_write(c->recv.buf, c->recv.len); // Send to UART
c->recv.len = 0; // Discard received data
} else if (ev == MG_EV_CLOSE) {
if (c->is_listening) s_tcp_conn = NULL;
}
(void) fnd, (void) evd;
}
// Software timer with a frequency close to the scheduling time slot
static void timer_fn(void *param) {
// Start listeners if they're stopped for any reason
struct mg_mgr *mgr = (struct mg_mgr *) param;
if (s_tcp_conn == NULL) s_tcp_conn = mg_listen(mgr, s_tcp_url, tcp_fn, 0);
if (s_ws_conn == NULL) s_ws_conn = mg_http_listen(mgr, s_ws_url, ws_fn, 0);
// Read UART
char buf[512];
int len = uart_read(buf, sizeof(buf));
if (len > 0) {
// Iterate over all connections. Send data to WS and TCP clients
for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
if (c->label[0] == 'W') mg_ws_send(c, buf, len, WEBSOCKET_OP_TEXT);
if (c->label[0] == 'T') mg_send(c, buf, len);
}
}
}
// HTTP request handler function
void uart_bridge_fn(struct mg_connection *c, int ev, void *ev_data,
void *fn_data) {
if (ev == MG_EV_OPEN && c->is_listening) {
mg_timer_add(c->mgr, 20, MG_TIMER_REPEAT, timer_fn, c->mgr);
uart_init(s_tx, s_rx, s_baud);
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_http_match_uri(hm, "/api/hi")) {
mg_http_reply(c, 200, "", "hi\n"); // Testing endpoint
} else if (mg_http_match_uri(hm, "/api/config/get")) {
mg_http_reply(
c, 200, "Content-Type: application/json\r\n",
"{\"tcp\":\"%s\",\"ws\":\"%s\",\"rx\":%d,\"tx\":%d,\"baud\":%d}\n",
s_tcp_url, s_ws_url, s_rx, s_tx, s_baud);
} else {
struct mg_http_serve_opts opts = {0};
#if 0
opts.root_dir = "/web_root";
opts.fs = &mg_fs_packed;
#else
opts.root_dir = "web_root";
#endif
mg_http_serve_dir(c, ev_data, &opts);
}
}
(void) fn_data;
}
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

+12
View File
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Device Dashboard</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css" />
</head>
<body></body>
<script type="module" src="main.js"></script>
</html>
+81
View File
@@ -0,0 +1,81 @@
'use strict';
import {Component, h, html, render, useEffect, useState, useRef} from './preact.min.js';
const Message = m => html`<span
style="color: ${m.message.uart ? '#444' : '#373'};">
${m.message.data}
</span>`;
const App = function(props) {
const [cfg, setCfg] = useState({tcp: '', ws: ''});
const [messages, setMessages] = useState([]);
const [txt, setTxt] = useState('');
const [connected, setConnected] = useState(false);
const [ws, setWs] = useState(null);
const tcp_port = cfg.tcp.split(':')[2] || 4001;
const ws_port = cfg.ws.split(':')[2] || 4002;
const refresh = () =>
fetch('/api/config/get').then(r => r.json()).then(r => setCfg(r));
const watchWebsocket = function() {
// Connect to websocker port, to implement WS console
var l = window.location, proto = l.protocol.replace('http', 'ws');
var tid, url = `${proto}//${l.hostname}:${ws_port}/ws`;
console.log(url);
var reconnect = function() {
var ws = new WebSocket(url);
ws.onopen = () => {
setConnected(true);
setWs(ws);
};
ws.onmessage = ev => {
// console.log(ev, ev.data);
setMessages(x => x.concat([{data: ev.data, uart: true}]));
};
ws.onclose = function() {
clearTimeout(tid);
tid = setTimeout(reconnect, 1000);
setConnected(false);
setWs(null);
};
};
reconnect();
};
useEffect(() => {
refresh();
watchWebsocket();
}, []);
const sendmessage = ev => {
setMessages(x => x.concat([{data: txt + '\n', uart: false}]));
if (ws) ws.send(txt + '\n');
setTxt('');
};
return html`
<div class="container">
<h1>UART \u27F7 network bridge </h1>
<h3>UART settings</h3>
<pre class="d-none">${JSON.stringify(cfg, null, 2)}</pre>
<p>RX pin ${cfg.rx}, TX pin ${cfg.tx}, baud ${cfg.baud}</p>
<h3>TCP listener: port ${tcp_port}</h3>
<p>Use netcat to connect:</p>
<div class="msg"> $ nc ${location.hostname} ${tcp_port} </div>
<h3>Websocket listener: port ${ws_port}</h3>
<div>UART console. Status: ${connected ? 'connected' : 'disconnected'}</div>
<pre style="height: 15em; overflow: auto;">
${messages.map(message => h(Message, {message}))}
</pre>
<div style="margin: 0.5em 0; display: flex">
<input placeholder="type and press enter..." style="flex: 1 100%;"
value=${txt} onchange=${sendmessage}
oninput=${ev => setTxt(ev.target.value)} />
</div>
</div>`;
};
window.onload = () => render(h(App), document.body);
+1
View File
@@ -0,0 +1 @@
var e,n,_,t,o,r,u,l={},i=[],c=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function s(e,n){for(var _ in n)e[_]=n[_];return e}function f(e){var n=e.parentNode;n&&n.removeChild(e)}function a(n,_,t){var o,r,u,l={};for(u in _)"key"==u?o=_[u]:"ref"==u?r=_[u]:l[u]=_[u];if(arguments.length>2&&(l.children=arguments.length>3?e.call(arguments,2):t),"function"==typeof n&&null!=n.defaultProps)for(u in n.defaultProps)void 0===l[u]&&(l[u]=n.defaultProps[u]);return p(n,l,o,r,null)}function p(e,t,o,r,u){var l={type:e,props:t,key:o,ref:r,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==u?++_:u};return null!=n.vnode&&n.vnode(l),l}function h(e){return e.children}function d(e,n){this.props=e,this.context=n}function v(e,n){if(null==n)return e.__?v(e.__,e.__.__k.indexOf(e)+1):null;for(var _;n<e.__k.length;n++)if(null!=(_=e.__k[n])&&null!=_.__e)return _.__e;return"function"==typeof e.type?v(e):null}function y(e){var n,_;if(null!=(e=e.__)&&null!=e.__c){for(e.__e=e.__c.base=null,n=0;n<e.__k.length;n++)if(null!=(_=e.__k[n])&&null!=_.__e){e.__e=e.__c.base=_.__e;break}return y(e)}}function m(e){(!e.__d&&(e.__d=!0)&&t.push(e)&&!g.__r++||r!==n.debounceRendering)&&((r=n.debounceRendering)||o)(g)}function g(){for(var e;g.__r=t.length;)e=t.sort(function(e,n){return e.__v.__b-n.__v.__b}),t=[],e.some(function(e){var n,_,t,o,r,u;e.__d&&(r=(o=(n=e).__v).__e,(u=n.__P)&&(_=[],(t=s({},o)).__v=o.__v+1,P(u,o,t,n.__n,void 0!==u.ownerSVGElement,null!=o.__h?[r]:null,_,null==r?v(o):r,o.__h),D(_,o),o.__e!=r&&y(o)))})}function k(e,n,_,t,o,r,u,c,s,f){var a,d,y,m,g,k,x,H=t&&t.__k||i,E=H.length;for(_.__k=[],a=0;a<n.length;a++)if(null!=(m=_.__k[a]=null==(m=n[a])||"boolean"==typeof m?null:"string"==typeof m||"number"==typeof m||"bigint"==typeof m?p(null,m,null,null,m):Array.isArray(m)?p(h,{children:m},null,null,null):m.__b>0?p(m.type,m.props,m.key,null,m.__v):m)){if(m.__=_,m.__b=_.__b+1,null===(y=H[a])||y&&m.key==y.key&&m.type===y.type)H[a]=void 0;else for(d=0;d<E;d++){if((y=H[d])&&m.key==y.key&&m.type===y.type){H[d]=void 0;break}y=null}P(e,m,y=y||l,o,r,u,c,s,f),g=m.__e,(d=m.ref)&&y.ref!=d&&(x||(x=[]),y.ref&&x.push(y.ref,null,m),x.push(d,m.__c||g,m)),null!=g?(null==k&&(k=g),"function"==typeof m.type&&null!=m.__k&&m.__k===y.__k?m.__d=s=b(m,s,e):s=C(e,m,y,H,g,s),f||"option"!==_.type?"function"==typeof _.type&&(_.__d=s):e.value=""):s&&y.__e==s&&s.parentNode!=e&&(s=v(y))}for(_.__e=k,a=E;a--;)null!=H[a]&&("function"==typeof _.type&&null!=H[a].__e&&H[a].__e==_.__d&&(_.__d=v(t,a+1)),U(H[a],H[a]));if(x)for(a=0;a<x.length;a++)T(x[a],x[++a],x[++a])}function b(e,n,_){var t,o;for(t=0;t<e.__k.length;t++)(o=e.__k[t])&&(o.__=e,n="function"==typeof o.type?b(o,n,_):C(_,o,o,e.__k,o.__e,n));return n}function C(e,n,_,t,o,r){var u,l,i;if(void 0!==n.__d)u=n.__d,n.__d=void 0;else if(null==_||o!=r||null==o.parentNode)e:if(null==r||r.parentNode!==e)e.appendChild(o),u=null;else{for(l=r,i=0;(l=l.nextSibling)&&i<t.length;i+=2)if(l==o)break e;e.insertBefore(o,r),u=r}return void 0!==u?u:o.nextSibling}function x(e,n,_){"-"===n[0]?e.setProperty(n,_):e[n]=null==_?"":"number"!=typeof _||c.test(n)?_:_+"px"}function H(e,n,_,t,o){var r;e:if("style"===n)if("string"==typeof _)e.style.cssText=_;else{if("string"==typeof t&&(e.style.cssText=t=""),t)for(n in t)_&&n in _||x(e.style,n,"");if(_)for(n in _)t&&_[n]===t[n]||x(e.style,n,_[n])}else if("o"===n[0]&&"n"===n[1])r=n!==(n=n.replace(/Capture$/,"")),n=n.toLowerCase()in e?n.toLowerCase().slice(2):n.slice(2),e.l||(e.l={}),e.l[n+r]=_,_?t||e.addEventListener(n,r?S:E,r):e.removeEventListener(n,r?S:E,r);else if("dangerouslySetInnerHTML"!==n){if(o)n=n.replace(/xlink[H:h]/,"h").replace(/sName$/,"s");else if("href"!==n&&"list"!==n&&"form"!==n&&"tabIndex"!==n&&"download"!==n&&n in e)try{e[n]=null==_?"":_;break e}catch(e){}"function"==typeof _||(null!=_&&(!1!==_||"a"===n[0]&&"r"===n[1])?e.setAttribute(n,_):e.removeAttribute(n))}}function E(e){this.l[e.type+!1](n.event?n.event(e):e)}function S(e){this.l[e.type+!0](n.event?n.event(e):e)}function P(e,_,t,o,r,u,l,i,c){var f,a,p,v,y,m,g,b,C,x,H,E=_.type;if(void 0!==_.constructor)return null;null!=t.__h&&(c=t.__h,i=_.__e=t.__e,_.__h=null,u=[i]),(f=n.__b)&&f(_);try{e:if("function"==typeof E){if(b=_.props,C=(f=E.contextType)&&o[f.__c],x=f?C?C.props.value:f.__:o,t.__c?g=(a=_.__c=t.__c).__=a.__E:("prototype"in E&&E.prototype.render?_.__c=a=new E(b,x):(_.__c=a=new d(b,x),a.constructor=E,a.render=A),C&&C.sub(a),a.props=b,a.state||(a.state={}),a.context=x,a.__n=o,p=a.__d=!0,a.__h=[]),null==a.__s&&(a.__s=a.state),null!=E.getDerivedStateFromProps&&(a.__s==a.state&&(a.__s=s({},a.__s)),s(a.__s,E.getDerivedStateFromProps(b,a.__s))),v=a.props,y=a.state,p)null==E.getDerivedStateFromProps&&null!=a.componentWillMount&&a.componentWillMount(),null!=a.componentDidMount&&a.__h.push(a.componentDidMount);else{if(null==E.getDerivedStateFromProps&&b!==v&&null!=a.componentWillReceiveProps&&a.componentWillReceiveProps(b,x),!a.__e&&null!=a.shouldComponentUpdate&&!1===a.shouldCompLine truncated
+23
View File
@@ -0,0 +1,23 @@
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; font: 16px sans-serif; }
select, input, label::before, textarea { outline: none; box-shadow:none !important; border: 1px solid #ccc !important; }
pre { color: #373; font-family: monospace; font-weight: bolder; font-size: smaller; background: #ddd; padding: 1em; border-radius: 0.2em; }
textarea, input, .addon { font-size: 15px; border: 1px solid #ccc; padding: 0.5em; }
a, a:visited, a:active { color: #55f; }
body {background: #455; }
.addon { background: #eee; min-width: 9em;}
.btn {
background: #ccc; border-radius: 0.3em; border: 0; color: #fff; cursor: pointer;
display: inline-block; padding: 0.6em 2em; font-weight: bolder;
}
.btn[disabled] { opacity: 0.5; cursor: auto;}
.smooth { transition: all .2s; }
.container { margin: 2em auto; max-width: 680px; background: white; padding: 1em; border-radius: 0.5em; }
.d-flex { display: flex; }
.d-none { display: none; }
.border { border: 1px solid #ddd; }
.rounded { border-radius: 0.5em; }
.nowrap { white-space: nowrap; }
.msg { background: #def; border-left: 5px solid #59d; padding: 0.5em; font-size: 90%; margin: 1em 0; }
.input { background: #fea; padding: 0.2em 1em; border-radius: 0.4em; }
.output { background: #aef; padding: 0.2em 1em; border-radius: 0.4em; }