mirror of
https://github.com/holub/mame
synced 2025-10-04 16:34:53 +03:00
Add an external panel for the Ensoniq VFX family of keyboards, with a websocket interface and an HTML/Javascript implementation that can be served over HTTP.
This commit is contained in:
parent
2924e3c174
commit
0206314395
373
src/emu/http.cpp
373
src/emu/http.cpp
@ -13,6 +13,10 @@ HTTP server handling
|
|||||||
#include "server_http.hpp"
|
#include "server_http.hpp"
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
|
||||||
const static struct mapping
|
const static struct mapping
|
||||||
{
|
{
|
||||||
const char* extension;
|
const char* extension;
|
||||||
@ -107,8 +111,153 @@ static std::string extension_to_type(const std::string& extension)
|
|||||||
return "text/plain";
|
return "text/plain";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** An HTTP Request. */
|
||||||
|
struct http_request_impl : public http_manager::http_request
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::shared_ptr<webpp::http_server::Request> m_request;
|
||||||
|
std::size_t m_query;
|
||||||
|
std::size_t m_fragment;
|
||||||
|
std::size_t m_path_end;
|
||||||
|
std::size_t m_query_end;
|
||||||
|
|
||||||
|
http_request_impl(std::shared_ptr<webpp::http_server::Request> request) : m_request(request) {
|
||||||
|
std::size_t len = m_request->path.length();
|
||||||
|
|
||||||
|
m_fragment = m_request->path.find('#');
|
||||||
|
m_query_end = m_fragment == std::string::npos ? len : m_fragment;
|
||||||
|
|
||||||
|
m_query = m_request->path.find('?');
|
||||||
|
m_path_end = m_query == std::string::npos ? m_query_end : m_query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Retrieves the requested resource. */
|
||||||
|
virtual const std::string get_resource() {
|
||||||
|
// The entire resource: path, query and fragment.
|
||||||
|
return m_request->path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the path part of the requested resource. */
|
||||||
|
virtual const std::string get_path() {
|
||||||
|
return m_request->path.substr(0, m_path_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the query part of the requested resource. */
|
||||||
|
virtual const std::string get_query() {
|
||||||
|
return m_query == std::string::npos ? "" : m_request->path.substr(m_query, m_query_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the fragment part of the requested resource. */
|
||||||
|
virtual const std::string get_fragment() {
|
||||||
|
return m_fragment == std::string::npos ? "" : m_request->path.substr(m_fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Retrieves a header from the HTTP request. */
|
||||||
|
virtual const std::string get_header(const std::string &header_name) {
|
||||||
|
auto i = m_request->header.find(header_name);
|
||||||
|
if (i != m_request->header.end()) {
|
||||||
|
return (*i).second;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Retrieves a header from the HTTP request. */
|
||||||
|
virtual const std::list<std::string> get_headers(const std::string &header_name) {
|
||||||
|
std::list<std::string> result;
|
||||||
|
auto range = m_request->header.equal_range(header_name);
|
||||||
|
for (auto i = range.first; i != range.second; i++) {
|
||||||
|
result.push_back((*i).second);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the body that was submitted with the HTTP request. */
|
||||||
|
virtual const std::string get_body() {
|
||||||
|
// TODO(cbrunschen): What to return here - http_server::Request has a 'content' feld that is never filled in!
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** An HTTP response. */
|
||||||
|
struct http_response_impl : public http_manager::http_response {
|
||||||
|
std::shared_ptr<webpp::http_server::Response> m_response;
|
||||||
|
int m_status;
|
||||||
|
std::string m_content_type;
|
||||||
|
std::stringstream m_headers;
|
||||||
|
std::stringstream m_body;
|
||||||
|
|
||||||
|
http_response_impl(std::shared_ptr<webpp::http_server::Response> response) : m_response(response) { }
|
||||||
|
|
||||||
|
/** Sets the HTTP status to be returned to the client. */
|
||||||
|
virtual void set_status(int status) {
|
||||||
|
m_status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the HTTP content type to be returned to the client. */
|
||||||
|
virtual void set_content_type(const std::string &content_type) {
|
||||||
|
m_content_type = content_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the body to be sent to the client. */
|
||||||
|
virtual void set_body(const std::string &body) {
|
||||||
|
m_body.str("");
|
||||||
|
append_body(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Appends something to the body to be sent to the client. */
|
||||||
|
virtual void append_body(const std::string &body) {
|
||||||
|
m_body << body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sends the response to the client. */
|
||||||
|
void send() {
|
||||||
|
m_response->type(m_content_type);
|
||||||
|
m_response->status(m_status);
|
||||||
|
m_response->send(m_body.str());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct websocket_endpoint_impl : public http_manager::websocket_endpoint {
|
||||||
|
/** The underlying edpoint. */
|
||||||
|
std::shared_ptr<webpp::ws_server::Endpoint> m_endpoint;
|
||||||
|
|
||||||
|
websocket_endpoint_impl(std::shared_ptr<webpp::ws_server::Endpoint> endpoint,
|
||||||
|
http_manager::websocket_open_handler on_open,
|
||||||
|
http_manager::websocket_message_handler on_message,
|
||||||
|
http_manager::websocket_close_handler on_close,
|
||||||
|
http_manager::websocket_error_handler on_error)
|
||||||
|
: m_endpoint(endpoint) {
|
||||||
|
this->on_open = on_open;
|
||||||
|
this->on_message = on_message;
|
||||||
|
this->on_close = on_close;
|
||||||
|
this->on_error = on_error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct websocket_connection_impl : public http_manager::websocket_connection {
|
||||||
|
/** The server */
|
||||||
|
webpp::ws_server *m_wsserver;
|
||||||
|
/* The underlying Commection. */
|
||||||
|
std::shared_ptr<webpp::ws_server::Connection> m_connection;
|
||||||
|
websocket_connection_impl(webpp::ws_server *server, std::shared_ptr<webpp::ws_server::Connection> connection)
|
||||||
|
: m_wsserver(server), m_connection(connection) { }
|
||||||
|
|
||||||
|
/** Sends a message to the client that is connected on the other end of this Websocket connection. */
|
||||||
|
virtual void send_message(const std::string &payload, int opcode) {
|
||||||
|
std::shared_ptr<webpp::ws_server::SendStream> message_stream = std::make_shared<webpp::ws_server::SendStream>();
|
||||||
|
(*message_stream) << payload;
|
||||||
|
m_wsserver->send(m_connection, message_stream, nullptr, opcode | 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Closes this open Websocket connection. */
|
||||||
|
virtual void close() {
|
||||||
|
m_wsserver->send_close(m_connection, 1000 /* normal close */);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
http_manager::http_manager(bool active, short port, const char *root)
|
http_manager::http_manager(bool active, short port, const char *root)
|
||||||
: m_io_context(std::make_shared<asio::io_context>())
|
: m_io_context(std::make_shared<asio::io_context>()), m_root(root)
|
||||||
{
|
{
|
||||||
if (!active) return;
|
if (!active) return;
|
||||||
|
|
||||||
@ -117,22 +266,20 @@ http_manager::http_manager(bool active, short port, const char *root)
|
|||||||
m_server->set_io_context(m_io_context);
|
m_server->set_io_context(m_io_context);
|
||||||
m_wsserver = std::make_unique<webpp::ws_server>();
|
m_wsserver = std::make_unique<webpp::ws_server>();
|
||||||
|
|
||||||
auto& endpoint = m_wsserver->endpoint["/"];
|
auto& endpoint = m_wsserver->m_endpoint["/"];
|
||||||
|
|
||||||
m_server->on_get([this, root](auto response, auto request) {
|
m_server->on_get([this, root](auto response, auto request) {
|
||||||
std::string doc_root = root;
|
std::string doc_root = root;
|
||||||
|
|
||||||
std::string path = request->path;
|
auto request_impl = std::make_shared<http_request_impl>(request);
|
||||||
|
|
||||||
|
std::string path = request_impl->get_path();
|
||||||
// If path ends in slash (i.e. is a directory) then add "index.html".
|
// If path ends in slash (i.e. is a directory) then add "index.html".
|
||||||
if (path[path.size() - 1] == '/')
|
if (path[path.size() - 1] == '/')
|
||||||
{
|
{
|
||||||
path += "index.html";
|
path += "index.html";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t last_qmark_pos = path.find_last_of("?");
|
|
||||||
if (last_qmark_pos != std::string::npos)
|
|
||||||
path = path.substr(0, last_qmark_pos - 1);
|
|
||||||
|
|
||||||
// Determine the file extension.
|
// Determine the file extension.
|
||||||
std::size_t last_slash_pos = path.find_last_of("/");
|
std::size_t last_slash_pos = path.find_last_of("/");
|
||||||
std::size_t last_dot_pos = path.find_last_of(".");
|
std::size_t last_dot_pos = path.find_last_of(".");
|
||||||
@ -195,19 +342,207 @@ http_manager::~http_manager()
|
|||||||
m_server_thread.join();
|
m_server_thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void on_get(http_manager::http_handler handler, std::shared_ptr<webpp::http_server::Response> response, std::shared_ptr<webpp::http_server::Request> request) {
|
||||||
|
auto request_impl = std::make_shared<http_request_impl>(request);
|
||||||
|
auto response_impl = std::make_shared<http_response_impl>(response);
|
||||||
|
|
||||||
void http_manager::update()
|
handler(request_impl, response_impl);
|
||||||
{
|
|
||||||
if (!m_server) return;
|
|
||||||
|
|
||||||
m_server->clear();
|
response_impl->send();
|
||||||
for (auto handler : m_handlers)
|
}
|
||||||
{
|
|
||||||
m_server->on_get(handler.first, [handler](auto response, auto request)
|
void http_manager::on_open(http_manager::websocket_endpoint_ptr endpoint, void *connection) {
|
||||||
{
|
std::lock_guard<std::mutex> lock(m_connections_mutex);
|
||||||
std::tuple<std::string,int, std::string> output = handler.second(request->path);
|
webpp::ws_server *ws_server = m_wsserver.get();
|
||||||
response->type(std::get<2>(output));
|
// Keep an oening shared_ptr to the Connection, so it won't go away while we are using it.
|
||||||
response->status(std::get<1>(output)).send(std::get<0>(output).c_str());
|
std::shared_ptr<webpp::ws_server::Connection> conn = (static_cast<webpp::ws_server::Connection *>(connection))->ptr();
|
||||||
});
|
http_manager::websocket_connection_ptr connection_impl = std::make_shared<websocket_connection_impl>(ws_server, conn);
|
||||||
|
m_connections[connection] = connection_impl;
|
||||||
|
|
||||||
|
if (endpoint->on_open) {
|
||||||
|
endpoint->on_open(connection_impl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void http_manager::on_message(http_manager::websocket_endpoint_ptr endpoint, void *connection, const std::string &payload, int opcode) {
|
||||||
|
if (endpoint->on_message) {
|
||||||
|
std::lock_guard<std::mutex> lock(m_connections_mutex);
|
||||||
|
auto i = m_connections.find(connection);
|
||||||
|
if (i != m_connections.end()) {
|
||||||
|
http_manager::websocket_connection_ptr websocket_connection_impl = (*i).second;
|
||||||
|
endpoint->on_message(websocket_connection_impl, payload, opcode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_manager::on_close(http_manager::websocket_endpoint_ptr endpoint, void *connection,
|
||||||
|
int status, const std::string& reason) {
|
||||||
|
std::lock_guard<std::mutex> lock(m_connections_mutex);
|
||||||
|
auto i = m_connections.find(connection);
|
||||||
|
if (i != m_connections.end()) {
|
||||||
|
if (endpoint->on_close) {
|
||||||
|
http_manager::websocket_connection_ptr websocket_connection_impl = (*i).second;
|
||||||
|
endpoint->on_close(websocket_connection_impl, status, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_connections.erase(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_manager::on_error(http_manager::websocket_endpoint_ptr endpoint, void *connection,
|
||||||
|
const std::error_code& error_code) {
|
||||||
|
std::lock_guard<std::mutex> lock(m_connections_mutex);
|
||||||
|
auto i = m_connections.find(connection);
|
||||||
|
if (i != m_connections.end()) {
|
||||||
|
if (endpoint->on_error) {
|
||||||
|
http_manager::websocket_connection_ptr websocket_connection_impl = (*i).second;
|
||||||
|
endpoint->on_error(websocket_connection_impl, error_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_connections.erase(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool http_manager::read_file(std::ostream &os, const std::string &path) {
|
||||||
|
std::ostringstream full_path;
|
||||||
|
full_path << m_root << path;
|
||||||
|
util::core_file::ptr f;
|
||||||
|
osd_file::error e = util::core_file::open(full_path.str(), OPEN_FLAG_READ, f);
|
||||||
|
if (e == osd_file::error::NONE)
|
||||||
|
{
|
||||||
|
int c;
|
||||||
|
while ((c = f->getc()) >= 0)
|
||||||
|
{
|
||||||
|
os.put(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e == osd_file::error::NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_manager::serve_document(http_request_ptr request, http_response_ptr response, const std::string &filename) {
|
||||||
|
std::ostringstream os;
|
||||||
|
if (read_file(os, filename))
|
||||||
|
{
|
||||||
|
response->set_status(200);
|
||||||
|
response->set_body(os.str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response->set_status(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_manager::serve_template(http_request_ptr request, http_response_ptr response,
|
||||||
|
const std::string &filename, substitution substitute, char init, char term)
|
||||||
|
{
|
||||||
|
// printf("webserver: serving template '%s' at path '%s'\n", filename.c_str(), request->get_path().c_str());
|
||||||
|
std::stringstream ss;
|
||||||
|
if (read_file(ss, filename))
|
||||||
|
{
|
||||||
|
std::ostringstream os;
|
||||||
|
while (ss.good())
|
||||||
|
{
|
||||||
|
std::string s;
|
||||||
|
getline(ss, s, init);
|
||||||
|
os << s;
|
||||||
|
if (ss.good())
|
||||||
|
{
|
||||||
|
// printf("webserver: found initiator '%c'\n", init);
|
||||||
|
getline(ss, s, term);
|
||||||
|
if (ss.good())
|
||||||
|
{
|
||||||
|
if (substitute(s))
|
||||||
|
{
|
||||||
|
os << s;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
os << init << s << term;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// printf("webserver: reached end before terminator\n");
|
||||||
|
os << init;
|
||||||
|
os << s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// printf("webserver: reached end before initiator\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response->set_status(200);
|
||||||
|
response->set_body(os.str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response->set_status(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_manager::add_http_handler(const std::string &path, http_manager::http_handler handler)
|
||||||
|
{
|
||||||
|
using namespace std::placeholders;
|
||||||
|
m_server->on_get(path, std::bind(on_get, handler, _1, _2));
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(m_handlers_mutex);
|
||||||
|
m_handlers.emplace(path, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_manager::remove_http_handler(const std::string &path) {
|
||||||
|
m_server->remove_handler(path);
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(m_handlers_mutex);
|
||||||
|
m_handlers.erase(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_manager::clear() {
|
||||||
|
m_server->clear();
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(m_handlers_mutex);
|
||||||
|
m_handlers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
http_manager::websocket_endpoint_ptr http_manager::add_endpoint(const std::string &path,
|
||||||
|
http_manager::websocket_open_handler on_open,
|
||||||
|
http_manager::websocket_message_handler on_message,
|
||||||
|
http_manager::websocket_close_handler on_close,
|
||||||
|
http_manager::websocket_error_handler on_error) {
|
||||||
|
auto i = m_endpoints.find(path);
|
||||||
|
if (i == m_endpoints.end()) {
|
||||||
|
using namespace std::placeholders;
|
||||||
|
|
||||||
|
auto &endpoint = m_wsserver->m_endpoint[path];
|
||||||
|
std::shared_ptr<webpp::ws_server::Endpoint> endpoint_ptr(&endpoint);
|
||||||
|
auto endpoint_impl = std::make_shared<websocket_endpoint_impl>(endpoint_ptr, on_open, on_message, on_close, on_error);
|
||||||
|
|
||||||
|
endpoint.on_open = [&, this, endpoint_impl](std::shared_ptr<webpp::ws_server::Connection> conn) {
|
||||||
|
this->on_open(endpoint_impl, conn.get());
|
||||||
|
};
|
||||||
|
|
||||||
|
endpoint.on_message = [&, this, endpoint_impl](std::shared_ptr<webpp::ws_server::Connection> conn, std::shared_ptr<webpp::ws_server::Message> message) {
|
||||||
|
std::string payload = message->string();
|
||||||
|
int opcode = message->fin_rsv_opcode & 0x0f;
|
||||||
|
this->on_message(endpoint_impl, conn.get(), payload, opcode);
|
||||||
|
};
|
||||||
|
|
||||||
|
endpoint.on_close = [&, this, endpoint_impl](std::shared_ptr<webpp::ws_server::Connection> conn, int status, const std::string& reason) {
|
||||||
|
this->on_close(endpoint_impl, conn.get(), status, reason);
|
||||||
|
};
|
||||||
|
|
||||||
|
endpoint.on_error = [&, this, endpoint_impl](std::shared_ptr<webpp::ws_server::Connection> conn, const std::error_code& error_code) {
|
||||||
|
this->on_error(endpoint_impl, conn.get(), error_code);
|
||||||
|
};
|
||||||
|
|
||||||
|
m_endpoints[path] = endpoint_impl;
|
||||||
|
return endpoint_impl;
|
||||||
|
} else {
|
||||||
|
return (*i).second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_manager::remove_endpoint(const std::string &path) {
|
||||||
|
m_endpoints.erase(path);
|
||||||
|
}
|
||||||
|
140
src/emu/http.h
140
src/emu/http.h
@ -39,18 +39,150 @@ class http_manager
|
|||||||
{
|
{
|
||||||
DISABLE_COPYING(http_manager);
|
DISABLE_COPYING(http_manager);
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
/** An HTTP Request. */
|
||||||
|
struct http_request : public std::enable_shared_from_this<http_request>
|
||||||
|
{
|
||||||
|
/** Retrieves the requested resource. */
|
||||||
|
virtual const std::string get_resource() = 0; // The entire resource: path, query and fragment.
|
||||||
|
|
||||||
|
/** Returns the path part of the requested resource. */
|
||||||
|
virtual const std::string get_path() = 0;
|
||||||
|
|
||||||
|
/** Returns the query part of the requested resource. */
|
||||||
|
virtual const std::string get_query() = 0;
|
||||||
|
|
||||||
|
/** Returns the fragment part of the requested resource. */
|
||||||
|
virtual const std::string get_fragment() = 0;
|
||||||
|
|
||||||
|
/** Retrieves a header from the HTTP request. */
|
||||||
|
virtual const std::string get_header(const std::string &header_name) = 0;
|
||||||
|
|
||||||
|
/** Retrieves a header from the HTTP request. */
|
||||||
|
virtual const std::list<std::string> get_headers(const std::string &header_name) = 0;
|
||||||
|
|
||||||
|
/** Returns the body that was submitted with the HTTP request. */
|
||||||
|
virtual const std::string get_body() = 0;
|
||||||
|
};
|
||||||
|
typedef std::shared_ptr<http_request> http_request_ptr;
|
||||||
|
|
||||||
|
/** An HTTP response. */
|
||||||
|
struct http_response : public std::enable_shared_from_this<http_response>
|
||||||
|
{
|
||||||
|
/** Sets the HTTP status to be returned to the client. */
|
||||||
|
virtual void set_status(int status) = 0;
|
||||||
|
|
||||||
|
/** Sets the HTTP content type to be returned to the client. */
|
||||||
|
virtual void set_content_type(const std::string &type) = 0;
|
||||||
|
|
||||||
|
/** Sets the body to be sent to the client. */
|
||||||
|
virtual void set_body(const std::string &body) = 0;
|
||||||
|
|
||||||
|
/** Appends something to the body to be sent to the client. */
|
||||||
|
virtual void append_body(const std::string &body) = 0;
|
||||||
|
};
|
||||||
|
typedef std::shared_ptr<http_response> http_response_ptr;
|
||||||
|
|
||||||
|
/** Identifies a Websocket connection. */
|
||||||
|
struct websocket_connection : public std::enable_shared_from_this<websocket_connection>
|
||||||
|
{
|
||||||
|
/** Sends a message to the client that is connected on the other end of this Websocket connection. */
|
||||||
|
virtual void send_message(const std::string &payload, int opcode) = 0;
|
||||||
|
|
||||||
|
/** Closes this open Websocket connection. */
|
||||||
|
virtual void close() = 0;
|
||||||
|
|
||||||
|
virtual ~websocket_connection() { }
|
||||||
|
};
|
||||||
|
typedef std::shared_ptr<websocket_connection> websocket_connection_ptr;
|
||||||
|
|
||||||
|
/** Handles opening an incoming Websocket connection. */
|
||||||
|
typedef std::function<void(websocket_connection_ptr connection)> websocket_open_handler;
|
||||||
|
|
||||||
|
/** Handles an incoming message on an open Websocket connection. */
|
||||||
|
typedef std::function<void(websocket_connection_ptr connection, const std::string &payload, int opcode)> websocket_message_handler;
|
||||||
|
|
||||||
|
/** Handles when the client has closed a websocket connection. */
|
||||||
|
typedef std::function<void(websocket_connection_ptr connection, int status, const std::string& reason)> websocket_close_handler;
|
||||||
|
|
||||||
|
/** Handles when there has been an error on a websocket connection. */
|
||||||
|
typedef std::function<void(websocket_connection_ptr connection, const std::error_code& error_code)> websocket_error_handler;
|
||||||
|
|
||||||
|
/** Handles an incoming HTTP request, generating an HTTP response. */
|
||||||
|
typedef std::function<void(http_request_ptr, http_response_ptr)> http_handler;
|
||||||
|
|
||||||
|
struct websocket_endpoint : public std::enable_shared_from_this<websocket_endpoint> {
|
||||||
|
websocket_open_handler on_open;
|
||||||
|
websocket_message_handler on_message;
|
||||||
|
websocket_close_handler on_close;
|
||||||
|
websocket_error_handler on_error;
|
||||||
|
};
|
||||||
|
typedef std::shared_ptr<websocket_endpoint> websocket_endpoint_ptr;
|
||||||
|
|
||||||
|
/** Substitutes one string with another, and returns whether the substitution should be performed.
|
||||||
|
* Used when evaluating a template. */
|
||||||
|
typedef std::function<bool(std::string &)> substitution;
|
||||||
|
|
||||||
http_manager(bool active, short port, const char *root);
|
http_manager(bool active, short port, const char *root);
|
||||||
virtual ~http_manager();
|
virtual ~http_manager();
|
||||||
|
void clear();
|
||||||
|
|
||||||
void clear() { m_handlers.clear(); update(); }
|
/** Adds a template to the web server. When the path is requested, the template will be read and parsed:
|
||||||
void add(const char *url, std::function<std::tuple<std::string,int,std::string>(std::string)> func) { m_handlers.emplace(url, func); }
|
* strings between each pair of <init, term> characters will be passed to the substitute function and the result
|
||||||
void update();
|
* will be used instead.
|
||||||
|
*/
|
||||||
|
void add_template(const std::string &path, substitution substitute, char init, char term);
|
||||||
|
|
||||||
|
/** Removes a template from the web server. */
|
||||||
|
void remove_template(const std::string &path);
|
||||||
|
|
||||||
|
/** Serves a template at an explicit path, which may be diffrent from the request path, under the document root.
|
||||||
|
* The template will be read and parsed:
|
||||||
|
* strings between each pair of <init, term> characters will be passed to the substitute function and the result
|
||||||
|
* will be used instead.
|
||||||
|
*/
|
||||||
|
void serve_template(http_request_ptr request, http_response_ptr response, const std::string &path, substitution substitute, char init, char term);
|
||||||
|
|
||||||
|
void serve_document(http_request_ptr request, http_response_ptr response, const std::string &path);
|
||||||
|
|
||||||
|
/** Adds an HTTP handler. When the specified path is requested, the specified HTTP handler will be called. */
|
||||||
|
void add_http_handler(const std::string &path, http_handler handler);
|
||||||
|
|
||||||
|
/** Removes the HTTP handler at the specified path. */
|
||||||
|
void remove_http_handler(const std::string &path);
|
||||||
|
|
||||||
|
/** Retrieves a websocket endpoint, possibly adding it if it does not already exist. */
|
||||||
|
websocket_endpoint_ptr add_endpoint(const std::string &path, websocket_open_handler on_open, websocket_message_handler on_message, websocket_close_handler on_close, websocket_error_handler on_error);
|
||||||
|
|
||||||
|
/** Removes the websocket endpoint at the specified path. */
|
||||||
|
void remove_endpoint(const std::string &path);
|
||||||
|
|
||||||
|
void on_open(http_manager::websocket_endpoint_ptr endpoint, void *onnection);
|
||||||
|
|
||||||
|
void on_message(http_manager::websocket_endpoint_ptr endpoint, void *connection, const std::string& payload, int opcode);
|
||||||
|
|
||||||
|
void on_close(http_manager::websocket_endpoint_ptr endpoint, void *connection, int status, const std::string& reason);
|
||||||
|
|
||||||
|
void on_error(http_manager::websocket_endpoint_ptr endpoint, void *connection, const std::error_code& error_code);
|
||||||
|
|
||||||
|
bool read_file(std::ostream &os, const std::string &path);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<asio::io_context> m_io_context;
|
std::shared_ptr<asio::io_context> m_io_context;
|
||||||
std::unique_ptr<webpp::http_server> m_server;
|
std::unique_ptr<webpp::http_server> m_server;
|
||||||
std::unique_ptr<webpp::ws_server> m_wsserver;
|
std::unique_ptr<webpp::ws_server> m_wsserver;
|
||||||
std::thread m_server_thread;
|
std::thread m_server_thread;
|
||||||
std::unordered_map<const char *, std::function<std::tuple<std::string, int, std::string>(std::string)>> m_handlers;
|
std::string m_root;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, http_handler> m_handlers;
|
||||||
|
std::mutex m_handlers_mutex;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, websocket_endpoint_ptr> m_endpoints;
|
||||||
|
std::mutex m_endpoints_mutex;
|
||||||
|
|
||||||
|
std::unordered_map<void *, websocket_connection_ptr> m_connections; // the keys are really webpp::ws_server::Connection pointers
|
||||||
|
std::mutex m_connections_mutex;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -344,8 +344,6 @@ int running_machine::run(bool quiet)
|
|||||||
|
|
||||||
export_http_api();
|
export_http_api();
|
||||||
|
|
||||||
m_manager.http()->update();
|
|
||||||
|
|
||||||
// run the CPUs until a reset or exit
|
// run the CPUs until a reset or exit
|
||||||
m_hard_reset_pending = false;
|
m_hard_reset_pending = false;
|
||||||
while ((!m_hard_reset_pending && !m_exit_pending) || m_saveload_schedule != saveload_schedule::NONE)
|
while ((!m_hard_reset_pending && !m_exit_pending) || m_saveload_schedule != saveload_schedule::NONE)
|
||||||
@ -1193,7 +1191,7 @@ running_machine::logerror_callback_item::logerror_callback_item(logerror_callbac
|
|||||||
|
|
||||||
void running_machine::export_http_api()
|
void running_machine::export_http_api()
|
||||||
{
|
{
|
||||||
m_manager.http()->add("/api/machine", [this](std::string)
|
m_manager.http()->add_http_handler("/api/machine", [this](http_manager::http_request_ptr request, http_manager::http_response_ptr response)
|
||||||
{
|
{
|
||||||
rapidjson::StringBuffer s;
|
rapidjson::StringBuffer s;
|
||||||
rapidjson::Writer<rapidjson::StringBuffer> writer(s);
|
rapidjson::Writer<rapidjson::StringBuffer> writer(s);
|
||||||
@ -1210,8 +1208,10 @@ void running_machine::export_http_api()
|
|||||||
|
|
||||||
writer.EndArray();
|
writer.EndArray();
|
||||||
writer.EndObject();
|
writer.EndObject();
|
||||||
|
|
||||||
return std::make_tuple(std::string(s.GetString()), 200, "application/json");
|
response->set_status(200);
|
||||||
|
response->set_content_type("application/json");
|
||||||
|
response->set_body(s.GetString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,6 +199,7 @@ namespace webpp {
|
|||||||
}
|
}
|
||||||
void clear()
|
void clear()
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_resource_mutex);
|
||||||
m_resource.clear();
|
m_resource.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,12 +207,12 @@ namespace webpp {
|
|||||||
|
|
||||||
std::function<void(std::shared_ptr<socket_type> socket, std::shared_ptr<typename ServerBase<socket_type>::Request>)> on_upgrade;
|
std::function<void(std::shared_ptr<socket_type> socket, std::shared_ptr<typename ServerBase<socket_type>::Request>)> on_upgrade;
|
||||||
private:
|
private:
|
||||||
/// Warning: do not add or remove resources after start() is called
|
/// Warning: do not access (red or write) m_resources without holding m_resource_mutex
|
||||||
std::map<regex_orderable, std::map<std::string, std::tuple<path2regex::Keys, http_handler>>> m_resource;
|
std::map<regex_orderable, std::map<std::string, std::tuple<path2regex::Keys, http_handler>>> m_resource;
|
||||||
|
std::mutex m_resource_mutex;
|
||||||
|
|
||||||
std::map<std::string, http_handler> m_default_resource;
|
std::map<std::string, http_handler> m_default_resource;
|
||||||
|
|
||||||
std::mutex m_resource_mutex;
|
|
||||||
public:
|
public:
|
||||||
virtual void start() {
|
virtual void start() {
|
||||||
if(!m_io_context)
|
if(!m_io_context)
|
||||||
|
@ -53,7 +53,7 @@ namespace webpp {
|
|||||||
public:
|
public:
|
||||||
virtual ~SocketServerBase() {}
|
virtual ~SocketServerBase() {}
|
||||||
|
|
||||||
class SendStream : public std::ostream {
|
class SendStream : public std::ostream, public std::enable_shared_from_this<SendStream> {
|
||||||
friend class SocketServerBase<socket_type>;
|
friend class SocketServerBase<socket_type>;
|
||||||
|
|
||||||
asio::streambuf streambuf;
|
asio::streambuf streambuf;
|
||||||
@ -65,7 +65,7 @@ namespace webpp {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class Connection {
|
class Connection : public std::enable_shared_from_this<Connection> {
|
||||||
friend class SocketServerBase<socket_type>;
|
friend class SocketServerBase<socket_type>;
|
||||||
friend class SocketServer<socket_type>;
|
friend class SocketServer<socket_type>;
|
||||||
|
|
||||||
@ -80,6 +80,10 @@ namespace webpp {
|
|||||||
|
|
||||||
std::string remote_endpoint_address;
|
std::string remote_endpoint_address;
|
||||||
unsigned short remote_endpoint_port;
|
unsigned short remote_endpoint_port;
|
||||||
|
|
||||||
|
std::shared_ptr<Connection> ptr() {
|
||||||
|
return this->shared_from_this();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit Connection(socket_type *socket): remote_endpoint_port(0), socket(socket), strand(socket->get_io_service()), closed(false) { }
|
explicit Connection(socket_type *socket): remote_endpoint_port(0), socket(socket), strand(socket->get_io_service()), closed(false) { }
|
||||||
@ -143,7 +147,7 @@ namespace webpp {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class Message : public std::istream {
|
class Message : public std::istream, public std::enable_shared_from_this<Message> {
|
||||||
friend class SocketServerBase<socket_type>;
|
friend class SocketServerBase<socket_type>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -163,7 +167,7 @@ namespace webpp {
|
|||||||
asio::streambuf streambuf;
|
asio::streambuf streambuf;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Endpoint {
|
class Endpoint : public std::enable_shared_from_this<Endpoint> {
|
||||||
friend class SocketServerBase<socket_type>;
|
friend class SocketServerBase<socket_type>;
|
||||||
std::unordered_set<std::shared_ptr<Connection> > connections;
|
std::unordered_set<std::shared_ptr<Connection> > connections;
|
||||||
std::mutex connections_mutex;
|
std::mutex connections_mutex;
|
||||||
@ -215,8 +219,9 @@ namespace webpp {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
public:
|
public:
|
||||||
/// Warning: do not add or remove endpoints after start() is called
|
/// Warning: do not access (red or write) m_endpoint without holding m_endpoint_mutex
|
||||||
std::map<regex_orderable, Endpoint> endpoint;
|
std::map<regex_orderable, Endpoint> m_endpoint;
|
||||||
|
std::mutex m_endpoint_mutex;
|
||||||
|
|
||||||
virtual void start() {
|
virtual void start() {
|
||||||
if(!io_context)
|
if(!io_context)
|
||||||
@ -247,7 +252,8 @@ namespace webpp {
|
|||||||
acceptor->close();
|
acceptor->close();
|
||||||
io_context->stop();
|
io_context->stop();
|
||||||
|
|
||||||
for(auto& p: endpoint)
|
std::lock_guard<std::mutex> lock(m_endpoint_mutex);
|
||||||
|
for(auto& p: m_endpoint)
|
||||||
p.second.connections.clear();
|
p.second.connections.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,7 +316,8 @@ namespace webpp {
|
|||||||
|
|
||||||
std::unordered_set<std::shared_ptr<Connection> > get_connections() {
|
std::unordered_set<std::shared_ptr<Connection> > get_connections() {
|
||||||
std::unordered_set<std::shared_ptr<Connection> > all_connections;
|
std::unordered_set<std::shared_ptr<Connection> > all_connections;
|
||||||
for(auto& e: endpoint) {
|
std::lock_guard<std::mutex> lock(m_endpoint_mutex);
|
||||||
|
for(auto& e: m_endpoint) {
|
||||||
std::lock_guard<std::mutex> lock(e.second.connections_mutex);
|
std::lock_guard<std::mutex> lock(e.second.connections_mutex);
|
||||||
all_connections.insert(e.second.connections.begin(), e.second.connections.end());
|
all_connections.insert(e.second.connections.begin(), e.second.connections.end());
|
||||||
}
|
}
|
||||||
@ -425,10 +432,11 @@ namespace webpp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_handshake(const std::shared_ptr<Connection> &connection, const std::shared_ptr<asio::streambuf> &read_buffer) {
|
void write_handshake(const std::shared_ptr<Connection> &connection, const std::shared_ptr<asio::streambuf> &read_buffer) {
|
||||||
//Find path- and method-match, and generate response
|
//Find path- and method-match, and generate response
|
||||||
for (auto ®ex_endpoint : endpoint) {
|
std::lock_guard<std::mutex> lock(m_endpoint_mutex);
|
||||||
|
for (auto ®ex_endpoint : m_endpoint) {
|
||||||
std::smatch path_match;
|
std::smatch path_match;
|
||||||
if(std::regex_match(connection->path, path_match, regex_endpoint.first)) {
|
if(std::regex_match(connection->path, path_match, regex_endpoint.first)) {
|
||||||
auto write_buffer = std::make_shared<asio::streambuf>();
|
auto write_buffer = std::make_shared<asio::streambuf>();
|
||||||
|
@ -612,7 +612,7 @@ static MACHINE_CONFIG_START( vfx )
|
|||||||
MCFG_CPU_ADD("esp", ES5510, XTAL_10MHz)
|
MCFG_CPU_ADD("esp", ES5510, XTAL_10MHz)
|
||||||
MCFG_DEVICE_DISABLE()
|
MCFG_DEVICE_DISABLE()
|
||||||
|
|
||||||
MCFG_ESQPANEL2X40_ADD("panel")
|
MCFG_ESQPANEL2X40_VFX_ADD("panel")
|
||||||
MCFG_ESQPANEL_TX_CALLBACK(DEVWRITELINE("duart", mc68681_device, rx_b_w))
|
MCFG_ESQPANEL_TX_CALLBACK(DEVWRITELINE("duart", mc68681_device, rx_b_w))
|
||||||
MCFG_ESQPANEL_ANALOG_CALLBACK(WRITE16(esq5505_state, analog_w))
|
MCFG_ESQPANEL_ANALOG_CALLBACK(WRITE16(esq5505_state, analog_w))
|
||||||
|
|
||||||
@ -654,7 +654,7 @@ static MACHINE_CONFIG_DERIVED(eps, vfx)
|
|||||||
MCFG_CPU_MODIFY( "maincpu" )
|
MCFG_CPU_MODIFY( "maincpu" )
|
||||||
MCFG_CPU_PROGRAM_MAP(eps_map)
|
MCFG_CPU_PROGRAM_MAP(eps_map)
|
||||||
|
|
||||||
MCFG_ESQPANEL_2X40_REMOVE("panel")
|
MCFG_ESQPANEL2X40_VFX_REMOVE("panel")
|
||||||
MCFG_ESQPANEL1X22_ADD("panel")
|
MCFG_ESQPANEL1X22_ADD("panel")
|
||||||
MCFG_ESQPANEL_TX_CALLBACK(DEVWRITELINE("duart", mc68681_device, rx_b_w))
|
MCFG_ESQPANEL_TX_CALLBACK(DEVWRITELINE("duart", mc68681_device, rx_b_w))
|
||||||
MCFG_ESQPANEL_ANALOG_CALLBACK(WRITE16(esq5505_state, analog_w))
|
MCFG_ESQPANEL_ANALOG_CALLBACK(WRITE16(esq5505_state, analog_w))
|
||||||
@ -689,7 +689,7 @@ static MACHINE_CONFIG_START(vfx32)
|
|||||||
MCFG_CPU_ADD("esp", ES5510, XTAL_10MHz)
|
MCFG_CPU_ADD("esp", ES5510, XTAL_10MHz)
|
||||||
MCFG_DEVICE_DISABLE()
|
MCFG_DEVICE_DISABLE()
|
||||||
|
|
||||||
MCFG_ESQPANEL2X40_ADD("panel")
|
MCFG_ESQPANEL2X40_VFX_ADD("panel")
|
||||||
MCFG_ESQPANEL_TX_CALLBACK(DEVWRITELINE("duart", mc68681_device, rx_b_w))
|
MCFG_ESQPANEL_TX_CALLBACK(DEVWRITELINE("duart", mc68681_device, rx_b_w))
|
||||||
MCFG_ESQPANEL_ANALOG_CALLBACK(WRITE16(esq5505_state, analog_w))
|
MCFG_ESQPANEL_ANALOG_CALLBACK(WRITE16(esq5505_state, analog_w))
|
||||||
|
|
||||||
@ -734,7 +734,7 @@ static MACHINE_CONFIG_DERIVED(sq1, vfx)
|
|||||||
MCFG_CPU_MODIFY( "maincpu" )
|
MCFG_CPU_MODIFY( "maincpu" )
|
||||||
MCFG_CPU_PROGRAM_MAP(sq1_map)
|
MCFG_CPU_PROGRAM_MAP(sq1_map)
|
||||||
|
|
||||||
MCFG_ESQPANEL_2X40_REMOVE("panel")
|
MCFG_ESQPANEL2X40_VFX_REMOVE("panel")
|
||||||
MCFG_ESQPANEL2X16_SQ1_ADD("panel")
|
MCFG_ESQPANEL2X16_SQ1_ADD("panel")
|
||||||
MCFG_ESQPANEL_TX_CALLBACK(DEVWRITELINE("duart", mc68681_device, rx_b_w))
|
MCFG_ESQPANEL_TX_CALLBACK(DEVWRITELINE("duart", mc68681_device, rx_b_w))
|
||||||
MCFG_ESQPANEL_ANALOG_CALLBACK(WRITE16(esq5505_state, analog_w))
|
MCFG_ESQPANEL_ANALOG_CALLBACK(WRITE16(esq5505_state, analog_w))
|
||||||
|
@ -6,6 +6,378 @@
|
|||||||
#include "emu.h"
|
#include "emu.h"
|
||||||
#include "esqpanel.h"
|
#include "esqpanel.h"
|
||||||
|
|
||||||
|
#define ESQPANEL_EXTERNAL_TIMER_ID 47000
|
||||||
|
|
||||||
|
//**************************************************************************
|
||||||
|
// External panel support
|
||||||
|
//**************************************************************************
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <set>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <list>
|
||||||
|
|
||||||
|
class external_panel;
|
||||||
|
|
||||||
|
using external_panel_ptr = std::shared_ptr<external_panel>;
|
||||||
|
typedef std::map<http_manager::websocket_connection_ptr, external_panel_ptr, std::owner_less<http_manager::websocket_connection_ptr>> connection_to_panel_map;
|
||||||
|
|
||||||
|
enum message_type {
|
||||||
|
UNKNOWN = 0,
|
||||||
|
ANALOG = 1 << 0,
|
||||||
|
BUTTON = 1 << 1,
|
||||||
|
CONTROL = 1 << 2,
|
||||||
|
DISPLAY = 1 << 3,
|
||||||
|
INFO = 1 << 4
|
||||||
|
};
|
||||||
|
|
||||||
|
class external_panel : public std::enable_shared_from_this<external_panel>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static int get_message_type(const char c)
|
||||||
|
{
|
||||||
|
switch(c)
|
||||||
|
{
|
||||||
|
case 'A':
|
||||||
|
return message_type::ANALOG;
|
||||||
|
case 'B':
|
||||||
|
return message_type::BUTTON;
|
||||||
|
case 'C':
|
||||||
|
return message_type::CONTROL;
|
||||||
|
case 'D':
|
||||||
|
return message_type::DISPLAY;
|
||||||
|
case 'I':
|
||||||
|
return message_type::INFO;
|
||||||
|
default:
|
||||||
|
return message_type::UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
external_panel() : m_send_message_types(0)
|
||||||
|
{
|
||||||
|
// printf("session: constructed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int handle_control_message(const std::string &command)
|
||||||
|
{
|
||||||
|
int old = m_send_message_types;
|
||||||
|
std::istringstream is(command);
|
||||||
|
if (get_message_type(is.get()) != message_type::CONTROL)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int n;
|
||||||
|
while (!is.eof()) {
|
||||||
|
char c = is.get();
|
||||||
|
int message_type = external_panel::get_message_type(c);
|
||||||
|
is >> n;
|
||||||
|
int send = (n != 0);
|
||||||
|
if (send)
|
||||||
|
{
|
||||||
|
m_send_message_types |= message_type;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_send_message_types &= ~message_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_send_message_types ^ old;
|
||||||
|
}
|
||||||
|
|
||||||
|
int send_message_types()
|
||||||
|
{
|
||||||
|
return m_send_message_types;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool send_display_data()
|
||||||
|
{
|
||||||
|
return m_send_message_types & message_type::DISPLAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool send_analog_values()
|
||||||
|
{
|
||||||
|
return m_send_message_types & message_type::ANALOG;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool send_buttons()
|
||||||
|
{
|
||||||
|
return m_send_message_types & message_type::BUTTON;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_send_message_types;
|
||||||
|
};
|
||||||
|
|
||||||
|
class esqpanel_external_panel_server
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum websocket_opcode {
|
||||||
|
text = 1,
|
||||||
|
binary = 2
|
||||||
|
};
|
||||||
|
esqpanel_external_panel_server(http_manager *webserver) :
|
||||||
|
m_server(webserver),
|
||||||
|
m_keyboard("unknown"),
|
||||||
|
m_version("1")
|
||||||
|
{
|
||||||
|
using namespace std::placeholders;
|
||||||
|
m_server->add_endpoint("/esqpanel/socket",
|
||||||
|
std::bind(&esqpanel_external_panel_server::on_open, this, _1),
|
||||||
|
std::bind(&esqpanel_external_panel_server::on_message, this, _1, _2, _3),
|
||||||
|
std::bind(&esqpanel_external_panel_server::on_close, this, _1, _2, _3),
|
||||||
|
std::bind(&esqpanel_external_panel_server::on_error, this, _1, _2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~esqpanel_external_panel_server()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void send_to_all(char c)
|
||||||
|
{
|
||||||
|
// printf("server: send_to_all(%02x)\n", ((unsigned int) c) & 0xff);
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||||
|
// printf("server: sending '%02x' to all\n", ((unsigned int) c) & 0xff);
|
||||||
|
m_to_send.str("");
|
||||||
|
m_to_send.put('D');
|
||||||
|
m_to_send.put(c);
|
||||||
|
const std::string &s = m_to_send.str();
|
||||||
|
|
||||||
|
for (auto iter: m_panels)
|
||||||
|
{
|
||||||
|
external_panel_ptr panel = iter.second;
|
||||||
|
if (panel->send_display_data())
|
||||||
|
{
|
||||||
|
send(iter.first, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_open(http_manager::websocket_connection_ptr connection)
|
||||||
|
{
|
||||||
|
using namespace std::placeholders;
|
||||||
|
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||||
|
m_panels[connection] = std::make_shared<external_panel>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_message(http_manager::websocket_connection_ptr connection, const std::string &payload, int opcode)
|
||||||
|
{
|
||||||
|
external_panel_ptr panel = external_panel_for_connection(connection);
|
||||||
|
const std::string &command = payload;
|
||||||
|
|
||||||
|
int t = external_panel::get_message_type(command.front());
|
||||||
|
|
||||||
|
if (t == message_type::CONTROL)
|
||||||
|
{
|
||||||
|
int changed = panel->handle_control_message(command);
|
||||||
|
// printf("server: control message, changed = '%x'\n", changed);
|
||||||
|
if ((changed & message_type::DISPLAY) && panel->send_display_data())
|
||||||
|
{
|
||||||
|
// printf("server: control message, sending contents\n");
|
||||||
|
send_contents(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((changed & message_type::ANALOG) && panel->send_analog_values())
|
||||||
|
{
|
||||||
|
// printf("server: control message, sending analog values\n");
|
||||||
|
send_analog_values(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((changed & message_type::BUTTON) && panel->send_buttons())
|
||||||
|
{
|
||||||
|
// printf("server: control message, sending button states\n");
|
||||||
|
send_button_states(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (t == message_type::INFO)
|
||||||
|
{
|
||||||
|
std::ostringstream o;
|
||||||
|
o << "I" << get_keyboard() << "," << get_version();
|
||||||
|
send(connection, o.str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||||
|
m_commands.emplace_back(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Echo the non-command message to any other connected panels that want it
|
||||||
|
for (auto iter: m_panels)
|
||||||
|
{
|
||||||
|
external_panel_ptr other_panel = iter.second;
|
||||||
|
if (other_panel != panel && (t & other_panel->send_message_types()) != 0)
|
||||||
|
{
|
||||||
|
send(iter.first, command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_close(http_manager::websocket_connection_ptr connection, int status, const std::string& reason)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||||
|
m_panels.erase(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_error(http_manager::websocket_connection_ptr connection, const std::error_code& error_code)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||||
|
m_panels.erase(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_document_request(http_manager::http_request_ptr request, http_manager::http_response_ptr response, const std::string &filename)
|
||||||
|
{
|
||||||
|
m_server->serve_document(request, response, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_template_request(http_manager::http_request_ptr request, http_manager::http_response_ptr response, const std::string &filename)
|
||||||
|
{
|
||||||
|
using namespace std::placeholders;
|
||||||
|
m_server->serve_template(request, response, filename, std::bind(&esqpanel_external_panel_server::get_template_value, this, _1), '$', '$');
|
||||||
|
}
|
||||||
|
|
||||||
|
external_panel_ptr external_panel_for_connection(http_manager::websocket_connection_ptr connection)
|
||||||
|
{
|
||||||
|
auto it = m_panels.find(connection);
|
||||||
|
|
||||||
|
if (it == m_panels.end()) {
|
||||||
|
// this connection is not in the list. This really shouldn't happen
|
||||||
|
// and probably means something else is wrong.
|
||||||
|
throw std::invalid_argument("No panel avaliable for connection");
|
||||||
|
}
|
||||||
|
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_commands()
|
||||||
|
{
|
||||||
|
// printf("server: has_commands()\n");
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||||
|
return !m_commands.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_next_command()
|
||||||
|
{
|
||||||
|
// printf("server: get_next_command()\n");
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||||
|
std::string command = std::move(m_commands.front());
|
||||||
|
m_commands.pop_front();
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_index(const std::string &index)
|
||||||
|
{
|
||||||
|
m_index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_http_document(const std::string &path, const std::string &filename)
|
||||||
|
{
|
||||||
|
m_server->remove_http_handler(path);
|
||||||
|
if (filename != "")
|
||||||
|
{
|
||||||
|
using namespace std::placeholders;
|
||||||
|
m_server->add_http_handler(path, std::bind(&esqpanel_external_panel_server::on_document_request, this, _1, _2, filename));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_http_template(const std::string &path, const std::string &filename)
|
||||||
|
{
|
||||||
|
m_server->remove_http_handler(path);
|
||||||
|
if (filename != "")
|
||||||
|
{
|
||||||
|
using namespace std::placeholders;
|
||||||
|
m_server->add_http_handler(path, std::bind(&esqpanel_external_panel_server::on_template_request, this, _1, _2, filename));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_content_provider(std::function<bool(std::ostream&)> provider)
|
||||||
|
{
|
||||||
|
m_content_provider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_keyboard(const std::string &keyboard)
|
||||||
|
{
|
||||||
|
m_keyboard = keyboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &get_keyboard() const
|
||||||
|
{
|
||||||
|
return m_keyboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &get_version() const
|
||||||
|
{
|
||||||
|
return m_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get_template_value(std::string &s)
|
||||||
|
{
|
||||||
|
if (s == "keyboard")
|
||||||
|
{
|
||||||
|
s = m_keyboard;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (s == "version")
|
||||||
|
{
|
||||||
|
s = m_version;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void send(http_manager::websocket_connection_ptr connection, const std::string &s)
|
||||||
|
{
|
||||||
|
connection->send_message(s, websocket_opcode::binary);
|
||||||
|
}
|
||||||
|
|
||||||
|
void send_contents(http_manager::websocket_connection_ptr connection)
|
||||||
|
{
|
||||||
|
if (m_content_provider)
|
||||||
|
{
|
||||||
|
m_to_send.str("");
|
||||||
|
m_to_send.put('D');
|
||||||
|
if (m_content_provider(m_to_send))
|
||||||
|
{
|
||||||
|
send(connection, m_to_send.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void send_analog_values(http_manager::websocket_connection_ptr connection)
|
||||||
|
{
|
||||||
|
// TODO(cbrunschen): get the current analog values and send them
|
||||||
|
}
|
||||||
|
|
||||||
|
void send_button_states(http_manager::websocket_connection_ptr connection)
|
||||||
|
{
|
||||||
|
// TODO(cbrunschen): track current button states and send them
|
||||||
|
}
|
||||||
|
|
||||||
|
http_manager *m_server;
|
||||||
|
std::recursive_mutex m_mutex;
|
||||||
|
|
||||||
|
connection_to_panel_map m_panels;
|
||||||
|
std::list<std::string> m_commands;
|
||||||
|
std::thread m_working_thread;
|
||||||
|
std::ostringstream m_to_send;
|
||||||
|
|
||||||
|
std::string m_index;
|
||||||
|
std::string m_keyboard;
|
||||||
|
std::string m_version;
|
||||||
|
std::function<bool(std::ostream&)> m_content_provider;
|
||||||
|
std::map<const std::string, const std::string> m_template_values;
|
||||||
|
};
|
||||||
|
|
||||||
//**************************************************************************
|
//**************************************************************************
|
||||||
// MACROS / CONSTANTS
|
// MACROS / CONSTANTS
|
||||||
//**************************************************************************
|
//**************************************************************************
|
||||||
@ -16,6 +388,7 @@
|
|||||||
|
|
||||||
DEFINE_DEVICE_TYPE(ESQPANEL1X22, esqpanel1x22_device, "esqpanel122", "Ensoniq front panel with 1x22 VFD")
|
DEFINE_DEVICE_TYPE(ESQPANEL1X22, esqpanel1x22_device, "esqpanel122", "Ensoniq front panel with 1x22 VFD")
|
||||||
DEFINE_DEVICE_TYPE(ESQPANEL2X40, esqpanel2x40_device, "esqpanel240", "Ensoniq front panel with 2x40 VFD")
|
DEFINE_DEVICE_TYPE(ESQPANEL2X40, esqpanel2x40_device, "esqpanel240", "Ensoniq front panel with 2x40 VFD")
|
||||||
|
DEFINE_DEVICE_TYPE(ESQPANEL2X40_VFX, esqpanel2x40_vfx_device, "esqpanel240_vfx", "Ensoniq front panel with 2x40 VFD for VFX family")
|
||||||
DEFINE_DEVICE_TYPE(ESQPANEL2X16_SQ1, esqpanel2x16_sq1_device, "esqpanel216_sq1", "Ensoniq front panel with 2x16 LCD")
|
DEFINE_DEVICE_TYPE(ESQPANEL2X16_SQ1, esqpanel2x16_sq1_device, "esqpanel216_sq1", "Ensoniq front panel with 2x16 LCD")
|
||||||
|
|
||||||
//**************************************************************************
|
//**************************************************************************
|
||||||
@ -29,6 +402,7 @@ DEFINE_DEVICE_TYPE(ESQPANEL2X16_SQ1, esqpanel2x16_sq1_device, "esqpanel216_sq1",
|
|||||||
esqpanel_device::esqpanel_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) :
|
esqpanel_device::esqpanel_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) :
|
||||||
device_t(mconfig, type, tag, owner, clock),
|
device_t(mconfig, type, tag, owner, clock),
|
||||||
device_serial_interface(mconfig, *this),
|
device_serial_interface(mconfig, *this),
|
||||||
|
m_light_states(0x3f), // maximum number of lights
|
||||||
m_write_tx(*this),
|
m_write_tx(*this),
|
||||||
m_write_analog(*this)
|
m_write_analog(*this)
|
||||||
{
|
{
|
||||||
@ -43,6 +417,19 @@ void esqpanel_device::device_start()
|
|||||||
{
|
{
|
||||||
m_write_tx.resolve_safe();
|
m_write_tx.resolve_safe();
|
||||||
m_write_analog.resolve_safe();
|
m_write_analog.resolve_safe();
|
||||||
|
|
||||||
|
m_external_panel_server = new esqpanel_external_panel_server(machine().manager().http());
|
||||||
|
m_external_panel_server->set_keyboard(owner()->shortname());
|
||||||
|
m_external_panel_server->set_index("/esqpanel/FrontPanel.html");
|
||||||
|
m_external_panel_server->add_http_template("/esqpanel/FrontPanel.html", get_front_panel_html_file());
|
||||||
|
m_external_panel_server->add_http_document("/esqpanel/FrontPanel.js", get_front_panel_js_file());
|
||||||
|
m_external_panel_server->set_content_provider([this](std::ostream& o)
|
||||||
|
{
|
||||||
|
return write_contents(o);
|
||||||
|
});
|
||||||
|
|
||||||
|
m_external_timer = timer_alloc(ESQPANEL_EXTERNAL_TIMER_ID);
|
||||||
|
m_external_timer->enable(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -61,11 +448,36 @@ void esqpanel_device::device_reset()
|
|||||||
m_xmit_read = m_xmit_write = 0;
|
m_xmit_read = m_xmit_write = 0;
|
||||||
m_bCalibSecondByte = false;
|
m_bCalibSecondByte = false;
|
||||||
m_bButtonLightSecondByte = false;
|
m_bButtonLightSecondByte = false;
|
||||||
|
|
||||||
|
attotime sample_time(0, ATTOSECONDS_PER_MILLISECOND);
|
||||||
|
attotime initial_delay(0, ATTOSECONDS_PER_MILLISECOND);
|
||||||
|
|
||||||
|
m_external_timer->adjust(initial_delay, 0, sample_time);
|
||||||
|
m_external_timer->enable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------
|
||||||
|
// device_stop - device-specific stop
|
||||||
|
//-------------------------------------------------
|
||||||
|
|
||||||
|
void esqpanel_device::device_stop()
|
||||||
|
{
|
||||||
|
device_t::device_stop();
|
||||||
|
|
||||||
|
delete m_external_panel_server;
|
||||||
|
m_external_panel_server = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void esqpanel_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
|
void esqpanel_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
|
||||||
{
|
{
|
||||||
device_serial_interface::device_timer(timer, id, param, ptr);
|
if (ESQPANEL_EXTERNAL_TIMER_ID == id)
|
||||||
|
{
|
||||||
|
check_external_panel_server();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
device_serial_interface::device_timer(timer, id, param, ptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void esqpanel_device::rcv_complete() // Rx completed receiving byte
|
void esqpanel_device::rcv_complete() // Rx completed receiving byte
|
||||||
@ -76,6 +488,7 @@ void esqpanel_device::rcv_complete() // Rx completed receiving byte
|
|||||||
// if (data >= 0xe0) printf("Got %02x from motherboard (second %s)\n", data, m_bCalibSecondByte ? "yes" : "no");
|
// if (data >= 0xe0) printf("Got %02x from motherboard (second %s)\n", data, m_bCalibSecondByte ? "yes" : "no");
|
||||||
|
|
||||||
send_to_display(data);
|
send_to_display(data);
|
||||||
|
m_external_panel_server->send_to_all(data);
|
||||||
|
|
||||||
if (m_bCalibSecondByte)
|
if (m_bCalibSecondByte)
|
||||||
{
|
{
|
||||||
@ -107,14 +520,14 @@ void esqpanel_device::rcv_complete() // Rx completed receiving byte
|
|||||||
// d Sounds
|
// d Sounds
|
||||||
// e 0
|
// e 0
|
||||||
// f Cart
|
// f Cart
|
||||||
// int lightNumber = data & 0x3f;
|
int lightNumber = data & 0x3f;
|
||||||
|
|
||||||
// Light states:
|
// Light states:
|
||||||
// 0 = Off
|
// 0 = Off
|
||||||
// 2 = On
|
// 2 = On
|
||||||
// 3 = Blinking
|
// 3 = Blinking
|
||||||
// int lightState = (data & 0xc0) >> 6;
|
m_light_states[lightNumber] = (data & 0xc0) >> 6;
|
||||||
|
|
||||||
// TODO: do something with the button information!
|
// TODO: do something with the button information!
|
||||||
// printf("Setting light %d to %s\n", lightNumber, lightState == 3 ? "Blink" : lightState == 2 ? "On" : "Off");
|
// printf("Setting light %d to %s\n", lightNumber, lightState == 3 ? "Blink" : lightState == 2 ? "On" : "Off");
|
||||||
m_bButtonLightSecondByte = false;
|
m_bButtonLightSecondByte = false;
|
||||||
@ -193,6 +606,39 @@ void esqpanel_device::xmit_char(uint8_t data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void esqpanel_device::check_external_panel_server() {
|
||||||
|
while (m_external_panel_server->has_commands())
|
||||||
|
{
|
||||||
|
std::string command = m_external_panel_server->get_next_command();
|
||||||
|
int l = command.length();
|
||||||
|
if (l > 0) {
|
||||||
|
std::istringstream is(command);
|
||||||
|
char c;
|
||||||
|
is >> c;
|
||||||
|
if (c == 'B') {
|
||||||
|
// button
|
||||||
|
char ud;
|
||||||
|
is >> ud;
|
||||||
|
int button;
|
||||||
|
is >> button;
|
||||||
|
bool down = ud == 'D';
|
||||||
|
uint8_t sendme = (down ? 0x80 : 0) | (button & 0xff);
|
||||||
|
// printf("button %d %s : sending char to mainboard: %02x\n", button, down ? "down" : "up", sendme);
|
||||||
|
xmit_char(sendme);
|
||||||
|
xmit_char(0x00);
|
||||||
|
} else if (c == 'A') {
|
||||||
|
// analog value from ES5505 OTIS: 10 bits, left-aligned within 16 bits.
|
||||||
|
int channel, value;
|
||||||
|
is >> channel;
|
||||||
|
is >> value;
|
||||||
|
uint16_t analog_value = (value << 6);
|
||||||
|
// printf("analog: channel %d, value %d = %04x\n", channel, value, analog_value);
|
||||||
|
set_analog_value(channel, analog_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void esqpanel_device::set_analog_value(offs_t offset, uint16_t value)
|
void esqpanel_device::set_analog_value(offs_t offset, uint16_t value)
|
||||||
{
|
{
|
||||||
m_write_analog(offset, value);
|
m_write_analog(offset, value);
|
||||||
@ -212,7 +658,7 @@ esqpanel1x22_device::esqpanel1x22_device(const machine_config &mconfig, const ch
|
|||||||
m_eps_mode = true;
|
m_eps_mode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* panel with 2x40 VFD display used in the ESQ-1, VFX-SD, SD-1, and others */
|
/* panel with 2x40 VFD display used in the ESQ-1, SQ-80 */
|
||||||
|
|
||||||
MACHINE_CONFIG_MEMBER(esqpanel2x40_device::device_add_mconfig)
|
MACHINE_CONFIG_MEMBER(esqpanel2x40_device::device_add_mconfig)
|
||||||
MCFG_ESQ2X40_ADD("vfd")
|
MCFG_ESQ2X40_ADD("vfd")
|
||||||
@ -226,6 +672,32 @@ esqpanel2x40_device::esqpanel2x40_device(const machine_config &mconfig, const ch
|
|||||||
m_eps_mode = false;
|
m_eps_mode = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* panel with 2x40 VFD display used in the VFX, VFX-SD, SD-1 series */
|
||||||
|
|
||||||
|
MACHINE_CONFIG_MEMBER(esqpanel2x40_vfx_device::device_add_mconfig)
|
||||||
|
MCFG_ESQ2X40_ADD("vfd")
|
||||||
|
MACHINE_CONFIG_END
|
||||||
|
|
||||||
|
esqpanel2x40_vfx_device::esqpanel2x40_vfx_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
|
||||||
|
esqpanel_device(mconfig, ESQPANEL2X40, tag, owner, clock),
|
||||||
|
m_vfd(*this, "vfd")
|
||||||
|
{
|
||||||
|
m_eps_mode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool esqpanel2x40_vfx_device::write_contents(std::ostream &o)
|
||||||
|
{
|
||||||
|
m_vfd->write_contents(o);
|
||||||
|
for (int i = 0; i < m_light_states.size(); i++)
|
||||||
|
{
|
||||||
|
o.put(0xff);
|
||||||
|
o.put((m_light_states[i] << 6) | i);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// --- SQ1 - Parduz --------------------------------------------------------------------------------------------------------------------------
|
// --- SQ1 - Parduz --------------------------------------------------------------------------------------------------------------------------
|
||||||
MACHINE_CONFIG_MEMBER(esqpanel2x16_sq1_device::device_add_mconfig)
|
MACHINE_CONFIG_MEMBER(esqpanel2x16_sq1_device::device_add_mconfig)
|
||||||
MCFG_ESQ2X16_SQ1_ADD("vfd")
|
MCFG_ESQ2X16_SQ1_ADD("vfd")
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
#include "machine/esqvfd.h"
|
#include "machine/esqvfd.h"
|
||||||
#include "machine/esqlcd.h"
|
#include "machine/esqlcd.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
//**************************************************************************
|
//**************************************************************************
|
||||||
// INTERFACE CONFIGURATION MACROS
|
// INTERFACE CONFIGURATION MACROS
|
||||||
//**************************************************************************
|
//**************************************************************************
|
||||||
@ -27,7 +29,16 @@
|
|||||||
#define MCFG_ESQPANEL2X40_REPLACE(_tag) \
|
#define MCFG_ESQPANEL2X40_REPLACE(_tag) \
|
||||||
MCFG_DEVICE_REPLACE(_tag, ESQPANEL2X40, 0)
|
MCFG_DEVICE_REPLACE(_tag, ESQPANEL2X40, 0)
|
||||||
|
|
||||||
#define MCFG_ESQPANEL_2X40_REMOVE(_tag) \
|
#define MCFG_ESQPANEL2X40_REMOVE(_tag) \
|
||||||
|
MCFG_DEVICE_REMOVE(_tag)
|
||||||
|
|
||||||
|
#define MCFG_ESQPANEL2X40_VFX_ADD(_tag) \
|
||||||
|
MCFG_DEVICE_ADD(_tag, ESQPANEL2X40_VFX, 0)
|
||||||
|
|
||||||
|
#define MCFG_ESQPANEL2X40_VFX_REPLACE(_tag) \
|
||||||
|
MCFG_DEVICE_REPLACE(_tag, ESQPANEL2X40_VFX, 0)
|
||||||
|
|
||||||
|
#define MCFG_ESQPANEL2X40_VFX_REMOVE(_tag) \
|
||||||
MCFG_DEVICE_REMOVE(_tag)
|
MCFG_DEVICE_REMOVE(_tag)
|
||||||
|
|
||||||
#define MCFG_ESQPANEL2X16_SQ1_ADD(_tag) \
|
#define MCFG_ESQPANEL2X16_SQ1_ADD(_tag) \
|
||||||
@ -51,11 +62,20 @@
|
|||||||
|
|
||||||
// ======================> esqpanel_device
|
// ======================> esqpanel_device
|
||||||
|
|
||||||
|
class esqpanel_external_panel_server;
|
||||||
|
|
||||||
class esqpanel_device : public device_t, public device_serial_interface
|
class esqpanel_device : public device_t, public device_serial_interface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
template <class Object> static devcb_base &set_tx_wr_callback(device_t &device, Object &&cb) { return downcast<esqpanel_device &>(device).m_write_tx.set_callback(std::forward<Object>(cb)); }
|
template <class Object>
|
||||||
template <class Object> static devcb_base &set_analog_wr_callback(device_t &device, Object &&cb) { return downcast<esqpanel_device &>(device).m_write_analog.set_callback(std::forward<Object>(cb)); }
|
static devcb_base &set_tx_wr_callback(device_t &device, Object &&cb) {
|
||||||
|
return downcast<esqpanel_device &>(device).m_write_tx.set_callback(std::forward<Object>(cb));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Object>
|
||||||
|
static devcb_base &set_analog_wr_callback(device_t &device, Object &&cb) {
|
||||||
|
return downcast<esqpanel_device &>(device).m_write_analog.set_callback(std::forward<Object>(cb));
|
||||||
|
}
|
||||||
|
|
||||||
void xmit_char(uint8_t data);
|
void xmit_char(uint8_t data);
|
||||||
void set_analog_value(offs_t offset, uint16_t value);
|
void set_analog_value(offs_t offset, uint16_t value);
|
||||||
@ -67,6 +87,7 @@ protected:
|
|||||||
// device-level overrides
|
// device-level overrides
|
||||||
virtual void device_start() override;
|
virtual void device_start() override;
|
||||||
virtual void device_reset() override;
|
virtual void device_reset() override;
|
||||||
|
virtual void device_stop() override;
|
||||||
virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;
|
virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;
|
||||||
|
|
||||||
// serial overrides
|
// serial overrides
|
||||||
@ -76,7 +97,17 @@ protected:
|
|||||||
|
|
||||||
virtual void send_to_display(uint8_t data) = 0;
|
virtual void send_to_display(uint8_t data) = 0;
|
||||||
|
|
||||||
bool m_eps_mode;
|
void check_external_panel_server();
|
||||||
|
|
||||||
|
virtual const std::string get_front_panel_html_file() const { return ""; }
|
||||||
|
virtual const std::string get_front_panel_js_file() const { return ""; }
|
||||||
|
virtual bool write_contents(std::ostream &o) { return false; }
|
||||||
|
|
||||||
|
std::vector<uint8_t> m_light_states;
|
||||||
|
|
||||||
|
bool m_eps_mode;
|
||||||
|
|
||||||
|
esqpanel_external_panel_server *m_external_panel_server;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const int XMIT_RING_SIZE = 16;
|
static const int XMIT_RING_SIZE = 16;
|
||||||
@ -89,6 +120,8 @@ private:
|
|||||||
uint8_t m_xmitring[XMIT_RING_SIZE];
|
uint8_t m_xmitring[XMIT_RING_SIZE];
|
||||||
int m_xmit_read, m_xmit_write;
|
int m_xmit_read, m_xmit_write;
|
||||||
bool m_tx_busy;
|
bool m_tx_busy;
|
||||||
|
|
||||||
|
emu_timer *m_external_timer;
|
||||||
};
|
};
|
||||||
|
|
||||||
class esqpanel1x22_device : public esqpanel_device {
|
class esqpanel1x22_device : public esqpanel_device {
|
||||||
@ -115,6 +148,26 @@ protected:
|
|||||||
required_device<esq2x40_device> m_vfd;
|
required_device<esq2x40_device> m_vfd;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class esqpanel2x40_vfx_device : public esqpanel_device {
|
||||||
|
public:
|
||||||
|
esqpanel2x40_vfx_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void device_add_mconfig(machine_config &config) override;
|
||||||
|
|
||||||
|
virtual void send_to_display(uint8_t data) override { m_vfd->write_char(data); }
|
||||||
|
|
||||||
|
virtual const std::string get_front_panel_html_file() const override { return "/esqpanel/vfx/FrontPanel.html"; }
|
||||||
|
virtual const std::string get_front_panel_js_file() const override { return "/esqpanel/vfx/FrontPanel.js"; }
|
||||||
|
virtual bool write_contents(std::ostream &o) override;
|
||||||
|
|
||||||
|
required_device<esq2x40_device> m_vfd;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const char *html;
|
||||||
|
static const char *js;
|
||||||
|
};
|
||||||
|
|
||||||
class esqpanel2x40_sq1_device : public esqpanel_device {
|
class esqpanel2x40_sq1_device : public esqpanel_device {
|
||||||
public:
|
public:
|
||||||
esqpanel2x40_sq1_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
|
esqpanel2x40_sq1_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
|
||||||
@ -142,6 +195,7 @@ protected:
|
|||||||
|
|
||||||
DECLARE_DEVICE_TYPE(ESQPANEL1X22, esqpanel1x22_device)
|
DECLARE_DEVICE_TYPE(ESQPANEL1X22, esqpanel1x22_device)
|
||||||
DECLARE_DEVICE_TYPE(ESQPANEL2X40, esqpanel2x40_device)
|
DECLARE_DEVICE_TYPE(ESQPANEL2X40, esqpanel2x40_device)
|
||||||
|
DECLARE_DEVICE_TYPE(ESQPANEL2X40_VFX, esqpanel2x40_vfx_device)
|
||||||
DECLARE_DEVICE_TYPE(ESQPANEL2X40_SQ1, esqpanel2x40_sq1_device)
|
DECLARE_DEVICE_TYPE(ESQPANEL2X40_SQ1, esqpanel2x40_sq1_device)
|
||||||
DECLARE_DEVICE_TYPE(ESQPANEL2X16_SQ1, esqpanel2x16_sq1_device)
|
DECLARE_DEVICE_TYPE(ESQPANEL2X16_SQ1, esqpanel2x16_sq1_device)
|
||||||
|
|
||||||
|
@ -225,13 +225,17 @@ void esq2x40_device::write_char(int data)
|
|||||||
switch (data)
|
switch (data)
|
||||||
{
|
{
|
||||||
case 0xd0: // blink start
|
case 0xd0: // blink start
|
||||||
m_curattr = AT_BLINK;
|
m_curattr |= AT_BLINK;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xd1: // blink stop (cancel all attribs on VFX+)
|
case 0xd1: // blink stop (cancel all attribs on VFX+)
|
||||||
m_curattr = 0; //&= ~AT_BLINK;
|
m_curattr = 0; //&= ~AT_BLINK;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 0xd2: // blinking underline on VFX
|
||||||
|
m_curattr |= AT_BLINK | AT_UNDERLINE;
|
||||||
|
break;
|
||||||
|
|
||||||
case 0xd3: // start underline
|
case 0xd3: // start underline
|
||||||
m_curattr |= AT_UNDERLINE;
|
m_curattr |= AT_UNDERLINE;
|
||||||
break;
|
break;
|
||||||
@ -278,6 +282,41 @@ void esq2x40_device::write_char(int data)
|
|||||||
update_display();
|
update_display();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool esq2x40_device::write_contents(std::ostream &o)
|
||||||
|
{
|
||||||
|
o.put((char) 0xd6); // clear screen
|
||||||
|
|
||||||
|
uint8_t attrs = 0;
|
||||||
|
for (int row = 0; row < 2; row++)
|
||||||
|
{
|
||||||
|
o.put((char) (0x80 + (40 * row))); // move to first column this row
|
||||||
|
|
||||||
|
for (int col = 0; col < 40; col++)
|
||||||
|
{
|
||||||
|
if (m_attrs[row][col] != attrs)
|
||||||
|
{
|
||||||
|
attrs = m_attrs[row][col];
|
||||||
|
|
||||||
|
o.put((char) 0xd1); // all attributes off
|
||||||
|
|
||||||
|
if (attrs & AT_BLINK)
|
||||||
|
{
|
||||||
|
o.put((char) 0xd0); // blink on
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attrs & AT_UNDERLINE)
|
||||||
|
{
|
||||||
|
o.put((char) 0xd3); // underline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
o.put((char) (m_chars[row][col] + ' '));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
esq2x40_device::esq2x40_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : esqvfd_device(mconfig, ESQ2X40, tag, owner, clock)
|
esq2x40_device::esq2x40_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : esqvfd_device(mconfig, ESQ2X40, tag, owner, clock)
|
||||||
{
|
{
|
||||||
m_rows = 2;
|
m_rows = 2;
|
||||||
|
@ -28,7 +28,8 @@ public:
|
|||||||
|
|
||||||
virtual void write_char(int data) = 0;
|
virtual void write_char(int data) = 0;
|
||||||
virtual void update_display();
|
virtual void update_display();
|
||||||
|
virtual bool write_contents(std::ostream &o) { return false; }
|
||||||
|
|
||||||
uint32_t conv_segments(uint16_t segin);
|
uint32_t conv_segments(uint16_t segin);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@ -71,6 +72,7 @@ public:
|
|||||||
esq2x40_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
|
esq2x40_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
|
||||||
|
|
||||||
virtual void write_char(int data) override;
|
virtual void write_char(int data) override;
|
||||||
|
virtual bool write_contents(std::ostream &o) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void device_add_mconfig(machine_config &config) override;
|
virtual void device_add_mconfig(machine_config &config) override;
|
||||||
|
25
web/esqpanel/vfx/FrontPanel.html
Normal file
25
web/esqpanel/vfx/FrontPanel.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Front Panel</title>
|
||||||
|
</head>
|
||||||
|
<body onload="prepare();" bgcolor="black" class="notranslate"">
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script language="javascript" src="FrontPanel.js"></script>
|
||||||
|
<script language="javascript">
|
||||||
|
function wsroot() {
|
||||||
|
var l = window.location.href;
|
||||||
|
return (l.startsWith("https") ? "wss" : "ws") + l.substring(l.indexOf(':'), l.lastIndexOf('/')) + "/socket";
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepare() {
|
||||||
|
var panelUrl = wsroot();
|
||||||
|
var panel = new fp.Panel(panelUrl, '$keyboard$', $version$);
|
||||||
|
document.body.appendChild(panel.container);
|
||||||
|
panel.display.clear();
|
||||||
|
panel.display.showString(0, 0, "Waiting to connect...");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
1209
web/esqpanel/vfx/FrontPanel.js
Normal file
1209
web/esqpanel/vfx/FrontPanel.js
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user