1 // license:BSD-3-Clause
2 // copyright-holders:R. Belmont, Parduz
3 /*
4 Ensoniq panel/display device
5 */
6 #include "emu.h"
7 #include "esqpanel.h"
8
9 #define ESQPANEL_EXTERNAL_TIMER_ID 47000
10
11 //**************************************************************************
12 // External panel support
13 //**************************************************************************
14
15 #include <list>
16 #include <mutex>
17 #include <set>
18 #include <sstream>
19 #include <string>
20 #include <thread>
21
22
23 namespace esqpanel {
24
25 class external_panel;
26
27 using external_panel_ptr = std::shared_ptr<external_panel>;
28 typedef std::map<http_manager::websocket_connection_ptr, external_panel_ptr, std::owner_less<http_manager::websocket_connection_ptr>> connection_to_panel_map;
29
30 enum message_type {
31 UNKNOWN = 0,
32 ANALOG = 1 << 0,
33 BUTTON = 1 << 1,
34 CONTROL = 1 << 2,
35 DISPLAY = 1 << 3,
36 INFO = 1 << 4
37 };
38
39 class external_panel
40 {
41 public:
get_message_type(const char c)42 static int get_message_type(const char c)
43 {
44 switch(c)
45 {
46 case 'A':
47 return message_type::ANALOG;
48 case 'B':
49 return message_type::BUTTON;
50 case 'C':
51 return message_type::CONTROL;
52 case 'D':
53 return message_type::DISPLAY;
54 case 'I':
55 return message_type::INFO;
56 default:
57 return message_type::UNKNOWN;
58 }
59 }
60
external_panel()61 external_panel() : m_send_message_types(0)
62 {
63 // printf("session: constructed\n");
64 }
65
handle_control_message(const std::string & command)66 int handle_control_message(const std::string &command)
67 {
68 int old = m_send_message_types;
69 std::istringstream is(command);
70 if (get_message_type(is.get()) != message_type::CONTROL)
71 {
72 return 0;
73 }
74
75 int n;
76 while (!is.eof()) {
77 char c = is.get();
78 int message_type = external_panel::get_message_type(c);
79 is >> n;
80 int send = (n != 0);
81 if (send)
82 {
83 m_send_message_types |= message_type;
84 }
85 else
86 {
87 m_send_message_types &= ~message_type;
88 }
89 }
90
91 return m_send_message_types ^ old;
92 }
93
send_message_types()94 int send_message_types()
95 {
96 return m_send_message_types;
97 }
98
send_display_data()99 bool send_display_data()
100 {
101 return m_send_message_types & message_type::DISPLAY;
102 }
103
send_analog_values()104 bool send_analog_values()
105 {
106 return m_send_message_types & message_type::ANALOG;
107 }
108
send_buttons()109 bool send_buttons()
110 {
111 return m_send_message_types & message_type::BUTTON;
112 }
113
114 private:
115 int m_send_message_types;
116 };
117
118 class external_panel_server
119 {
120 public:
121 enum websocket_opcode {
122 text = 1,
123 binary = 2
124 };
external_panel_server(http_manager * webserver)125 external_panel_server(http_manager *webserver) :
126 m_server(webserver),
127 m_keyboard("unknown"),
128 m_version("1")
129 {
130 using namespace std::placeholders;
131 if (m_server->is_active()) {
132 m_server->add_endpoint("/esqpanel/socket",
133 std::bind(&external_panel_server::on_open, this, _1),
134 std::bind(&external_panel_server::on_message, this, _1, _2, _3),
135 std::bind(&external_panel_server::on_close, this, _1, _2, _3),
136 std::bind(&external_panel_server::on_error, this, _1, _2)
137 );
138 }
139 }
140
~external_panel_server()141 virtual ~external_panel_server()
142 {
143 if (m_server->is_active()) {
144 m_server->remove_endpoint("/esqpanel/socket");
145 }
146 }
147
send_to_all(char c)148 void send_to_all(char c)
149 {
150 // printf("server: send_to_all(%02x)\n", ((unsigned int) c) & 0xff);
151 std::lock_guard<std::recursive_mutex> lock(m_mutex);
152 // printf("server: sending '%02x' to all\n", ((unsigned int) c) & 0xff);
153 m_to_send.str("");
154 m_to_send.put('D');
155 m_to_send.put(c);
156 const std::string &s = m_to_send.str();
157
158 for (const auto &iter: m_panels)
159 {
160 external_panel_ptr panel = iter.second;
161 if (panel->send_display_data())
162 {
163 send(iter.first, s);
164 }
165 }
166 }
167
on_open(http_manager::websocket_connection_ptr connection)168 void on_open(http_manager::websocket_connection_ptr connection)
169 {
170 using namespace std::placeholders;
171
172 std::lock_guard<std::recursive_mutex> lock(m_mutex);
173 m_panels[connection] = std::make_shared<external_panel>();
174 }
175
on_message(http_manager::websocket_connection_ptr connection,const std::string & payload,int opcode)176 void on_message(http_manager::websocket_connection_ptr connection, const std::string &payload, int opcode)
177 {
178 external_panel_ptr panel = external_panel_for_connection(connection);
179 const std::string &command = payload;
180
181 int t = external_panel::get_message_type(command.front());
182
183 if (t == message_type::CONTROL)
184 {
185 int changed = panel->handle_control_message(command);
186 // printf("server: control message, changed = '%x'\n", changed);
187 if ((changed & message_type::DISPLAY) && panel->send_display_data())
188 {
189 // printf("server: control message, sending contents\n");
190 send_contents(connection);
191 }
192
193 if ((changed & message_type::ANALOG) && panel->send_analog_values())
194 {
195 // printf("server: control message, sending analog values\n");
196 send_analog_values(connection);
197 }
198
199 if ((changed & message_type::BUTTON) && panel->send_buttons())
200 {
201 // printf("server: control message, sending button states\n");
202 send_button_states(connection);
203 }
204 }
205 else if (t == message_type::INFO)
206 {
207 std::ostringstream o;
208 o << "I" << get_keyboard() << "," << get_version();
209 send(connection, o.str());
210 }
211 else
212 {
213 {
214 std::lock_guard<std::recursive_mutex> lock(m_mutex);
215 m_commands.emplace_back(command);
216 }
217
218 // Echo the non-command message to any other connected panels that want it
219 for (const auto &iter: m_panels)
220 {
221 external_panel_ptr other_panel = iter.second;
222 if (other_panel != panel && (t & other_panel->send_message_types()) != 0)
223 {
224 send(iter.first, command);
225 }
226 }
227 }
228 }
229
on_close(http_manager::websocket_connection_ptr connection,int status,const std::string & reason)230 void on_close(http_manager::websocket_connection_ptr connection, int status, const std::string& reason)
231 {
232 std::lock_guard<std::recursive_mutex> lock(m_mutex);
233 m_panels.erase(connection);
234 }
235
on_error(http_manager::websocket_connection_ptr connection,const std::error_code & error_code)236 void on_error(http_manager::websocket_connection_ptr connection, const std::error_code& error_code)
237 {
238 std::lock_guard<std::recursive_mutex> lock(m_mutex);
239 m_panels.erase(connection);
240 }
241
on_document_request(http_manager::http_request_ptr request,http_manager::http_response_ptr response,const std::string & filename)242 void on_document_request(http_manager::http_request_ptr request, http_manager::http_response_ptr response, const std::string &filename)
243 {
244 m_server->serve_document(request, response, filename);
245 }
246
on_template_request(http_manager::http_request_ptr request,http_manager::http_response_ptr response,const std::string & filename)247 void on_template_request(http_manager::http_request_ptr request, http_manager::http_response_ptr response, const std::string &filename)
248 {
249 using namespace std::placeholders;
250 m_server->serve_template(request, response, filename, std::bind(&external_panel_server::get_template_value, this, _1), '$', '$');
251 }
252
external_panel_for_connection(http_manager::websocket_connection_ptr connection)253 external_panel_ptr external_panel_for_connection(http_manager::websocket_connection_ptr connection)
254 {
255 auto it = m_panels.find(connection);
256
257 if (it == m_panels.end()) {
258 // this connection is not in the list. This really shouldn't happen
259 // and probably means something else is wrong.
260 throw std::invalid_argument("No panel avaliable for connection");
261 }
262
263 return it->second;
264 }
265
has_commands()266 bool has_commands()
267 {
268 // printf("server: has_commands()\n");
269 std::lock_guard<std::recursive_mutex> lock(m_mutex);
270 return !m_commands.empty();
271 }
272
get_next_command()273 std::string get_next_command()
274 {
275 // printf("server: get_next_command()\n");
276 std::lock_guard<std::recursive_mutex> lock(m_mutex);
277 std::string command = std::move(m_commands.front());
278 m_commands.pop_front();
279 return command;
280 }
281
set_index(const std::string & index)282 void set_index(const std::string &index)
283 {
284 m_index = index;
285 }
286
add_http_document(const std::string & path,const std::string & filename)287 void add_http_document(const std::string &path, const std::string &filename)
288 {
289 m_server->remove_http_handler(path);
290 if (filename != "")
291 {
292 using namespace std::placeholders;
293 m_server->add_http_handler(path, std::bind(&external_panel_server::on_document_request, this, _1, _2, filename));
294 }
295 }
296
add_http_template(const std::string & path,const std::string & filename)297 void add_http_template(const std::string &path, const std::string &filename)
298 {
299 m_server->remove_http_handler(path);
300 if (filename != "")
301 {
302 using namespace std::placeholders;
303 m_server->add_http_handler(path, std::bind(&external_panel_server::on_template_request, this, _1, _2, filename));
304 }
305 }
306
set_content_provider(std::function<bool (std::ostream &)> provider)307 void set_content_provider(std::function<bool(std::ostream&)> provider)
308 {
309 m_content_provider = provider;
310 }
311
set_keyboard(const std::string & keyboard)312 void set_keyboard(const std::string &keyboard)
313 {
314 m_keyboard = keyboard;
315 }
316
get_keyboard() const317 const std::string &get_keyboard() const
318 {
319 return m_keyboard;
320 }
321
get_version() const322 const std::string &get_version() const
323 {
324 return m_version;
325 }
326
get_template_value(std::string & s)327 bool get_template_value(std::string &s)
328 {
329 if (s == "keyboard")
330 {
331 s = m_keyboard;
332 return true;
333 }
334 else if (s == "version")
335 {
336 s = m_version;
337 return true;
338 }
339 else
340 {
341 return false;
342 }
343 }
344
345 private:
send(http_manager::websocket_connection_ptr connection,const std::string & s)346 void send(http_manager::websocket_connection_ptr connection, const std::string &s)
347 {
348 connection->send_message(s, websocket_opcode::binary);
349 }
350
send_contents(http_manager::websocket_connection_ptr connection)351 void send_contents(http_manager::websocket_connection_ptr connection)
352 {
353 if (m_content_provider)
354 {
355 m_to_send.str("");
356 m_to_send.put('D');
357 if (m_content_provider(m_to_send))
358 {
359 send(connection, m_to_send.str());
360 }
361 }
362 }
363
send_analog_values(http_manager::websocket_connection_ptr connection)364 void send_analog_values(http_manager::websocket_connection_ptr connection)
365 {
366 // TODO(cbrunschen): get the current analog values and send them
367 }
368
send_button_states(http_manager::websocket_connection_ptr connection)369 void send_button_states(http_manager::websocket_connection_ptr connection)
370 {
371 // TODO(cbrunschen): track current button states and send them
372 }
373
374 http_manager *m_server;
375 std::recursive_mutex m_mutex;
376
377 connection_to_panel_map m_panels;
378 std::list<std::string> m_commands;
379 std::thread m_working_thread;
380 std::ostringstream m_to_send;
381
382 std::string m_index;
383 std::string m_keyboard;
384 std::string m_version;
385 std::function<bool(std::ostream&)> m_content_provider;
386 std::map<const std::string, const std::string> m_template_values;
387 };
388
389 } // namespace esqpanel
390
391 //**************************************************************************
392 // MACROS / CONSTANTS
393 //**************************************************************************
394
395 //**************************************************************************
396 // DEVICE DEFINITIONS
397 //**************************************************************************
398
399 DEFINE_DEVICE_TYPE(ESQPANEL1X22, esqpanel1x22_device, "esqpanel122", "Ensoniq front panel with 1x22 VFD")
400 DEFINE_DEVICE_TYPE(ESQPANEL2X40, esqpanel2x40_device, "esqpanel240", "Ensoniq front panel with 2x40 VFD")
401 DEFINE_DEVICE_TYPE(ESQPANEL2X40_VFX, esqpanel2x40_vfx_device, "esqpanel240_vfx", "Ensoniq front panel with 2x40 VFD for VFX family")
402 DEFINE_DEVICE_TYPE(ESQPANEL2X16_SQ1, esqpanel2x16_sq1_device, "esqpanel216_sq1", "Ensoniq front panel with 2x16 LCD")
403
404 //**************************************************************************
405 // LIVE DEVICE
406 //**************************************************************************
407
408 //-------------------------------------------------
409 // esqpanel_device - constructor
410 //-------------------------------------------------
411
esqpanel_device(const machine_config & mconfig,device_type type,const char * tag,device_t * owner,uint32_t clock)412 esqpanel_device::esqpanel_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) :
413 device_t(mconfig, type, tag, owner, clock),
414 device_serial_interface(mconfig, *this),
415 m_light_states(0x3f), // maximum number of lights
416 m_write_tx(*this),
417 m_write_analog(*this)
418 {
419 std::fill(std::begin(m_xmitring), std::end(m_xmitring), 0);
420 }
421
422
423 //-------------------------------------------------
424 // device_start - device-specific startup
425 //-------------------------------------------------
426
device_start()427 void esqpanel_device::device_start()
428 {
429 m_write_tx.resolve_safe();
430 m_write_analog.resolve_safe();
431
432 m_external_panel_server = new esqpanel::external_panel_server(machine().manager().http());
433 if (machine().manager().http()->is_active()) {
434 m_external_panel_server->set_keyboard(owner()->shortname());
435 m_external_panel_server->set_index("/esqpanel/FrontPanel.html");
436 m_external_panel_server->add_http_template("/esqpanel/FrontPanel.html", get_front_panel_html_file());
437 m_external_panel_server->add_http_document("/esqpanel/FrontPanel.js", get_front_panel_js_file());
438 m_external_panel_server->set_content_provider([this](std::ostream& o)
439 {
440 return write_contents(o);
441 });
442
443 m_external_timer = timer_alloc(ESQPANEL_EXTERNAL_TIMER_ID);
444 m_external_timer->enable(false);
445 }
446 }
447
448
449 //-------------------------------------------------
450 // device_reset - device-specific reset
451 //-------------------------------------------------
452
device_reset()453 void esqpanel_device::device_reset()
454 {
455 // panel comms is at 62500 baud (double the MIDI rate), 8N2
456 set_data_frame(1, 8, PARITY_NONE, STOP_BITS_2);
457 set_rcv_rate(62500);
458 set_tra_rate(62500);
459
460 m_tx_busy = false;
461 m_xmit_read = m_xmit_write = 0;
462 m_bCalibSecondByte = false;
463 m_bButtonLightSecondByte = false;
464
465 attotime sample_time(0, ATTOSECONDS_PER_MILLISECOND);
466 attotime initial_delay(0, ATTOSECONDS_PER_MILLISECOND);
467
468 if (m_external_timer) {
469 m_external_timer->adjust(initial_delay, 0, sample_time);
470 m_external_timer->enable(true);
471 }
472 }
473
474 //-------------------------------------------------
475 // device_stop - device-specific stop
476 //-------------------------------------------------
477
device_stop()478 void esqpanel_device::device_stop()
479 {
480 device_t::device_stop();
481
482 delete m_external_panel_server;
483 m_external_panel_server = nullptr;
484 }
485
device_timer(emu_timer & timer,device_timer_id id,int param,void * ptr)486 void esqpanel_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
487 {
488 if (ESQPANEL_EXTERNAL_TIMER_ID == id)
489 {
490 check_external_panel_server();
491 }
492 }
493
rcv_complete()494 void esqpanel_device::rcv_complete() // Rx completed receiving byte
495 {
496 receive_register_extract();
497 uint8_t data = get_received_char();
498
499 // if (data >= 0xe0) printf("Got %02x from motherboard (second %s)\n", data, m_bCalibSecondByte ? "yes" : "no");
500
501 send_to_display(data);
502 m_external_panel_server->send_to_all(data);
503
504 if (m_bCalibSecondByte)
505 {
506 // printf("second byte is %02x\n", data);
507 if (data == 0xfd) // calibration request
508 {
509 // printf("let's send reply!\n");
510 xmit_char(0xff); // this is the correct response for "calibration OK"
511 }
512 m_bCalibSecondByte = false;
513 }
514 else if (m_bButtonLightSecondByte)
515 {
516 // Lights on the Buttons, on the VFX-SD:
517 // Number Button
518 // 0 1-6
519 // 1 8
520 // 2 6
521 // 3 4
522 // 4 2
523 // 5 Compare
524 // 6 1
525 // 7 Presets
526 // 8 7-12
527 // 9 9
528 // a 7
529 // b 5
530 // c 3
531 // d Sounds
532 // e 0
533 // f Cart
534 int lightNumber = data & 0x3f;
535
536 // Light states:
537 // 0 = Off
538 // 2 = On
539 // 3 = Blinking
540 m_light_states[lightNumber] = (data & 0xc0) >> 6;
541
542 // TODO: do something with the button information!
543 // printf("Setting light %d to %s\n", lightNumber, lightState == 3 ? "Blink" : lightState == 2 ? "On" : "Off");
544 m_bButtonLightSecondByte = false;
545 }
546 else if (data == 0xfb) // request calibration
547 {
548 m_bCalibSecondByte = true;
549 }
550 else if (data == 0xff) // button light state command
551 {
552 m_bButtonLightSecondByte = true;
553 }
554 else
555 {
556 // EPS wants a throwaway reply byte for each byte sent to the KPC
557 // VFX-SD and SD-1 definitely don't :)
558 if (m_eps_mode)
559 {
560 if (data == 0xe7)
561 {
562 xmit_char(0x00); // actual value of response is never checked
563 }
564 else if (data == 0x71)
565 {
566 xmit_char(0x00); // actual value of response is never checked
567 }
568 else
569 {
570 xmit_char(data); // actual value of response is never checked
571 }
572 }
573 }
574 }
575
tra_complete()576 void esqpanel_device::tra_complete() // Tx completed sending byte
577 {
578 // printf("panel Tx complete\n");
579 // is there more waiting to send?
580 if (m_xmit_read != m_xmit_write)
581 {
582 transmit_register_setup(m_xmitring[m_xmit_read++]);
583 if (m_xmit_read >= XMIT_RING_SIZE)
584 {
585 m_xmit_read = 0;
586 }
587 }
588 else
589 {
590 m_tx_busy = false;
591 }
592 }
593
tra_callback()594 void esqpanel_device::tra_callback() // Tx send bit
595 {
596 m_write_tx(transmit_register_get_data_bit());
597 }
598
xmit_char(uint8_t data)599 void esqpanel_device::xmit_char(uint8_t data)
600 {
601 // printf("Panel: xmit %02x\n", data);
602
603 // if tx is busy it'll pick this up automatically when it completes
604 if (!m_tx_busy)
605 {
606 m_tx_busy = true;
607 transmit_register_setup(data);
608 }
609 else
610 {
611 // tx is busy, it'll pick this up next time
612 m_xmitring[m_xmit_write++] = data;
613 if (m_xmit_write >= XMIT_RING_SIZE)
614 {
615 m_xmit_write = 0;
616 }
617 }
618 }
619
check_external_panel_server()620 void esqpanel_device::check_external_panel_server() {
621 while (m_external_panel_server->has_commands())
622 {
623 std::string command = m_external_panel_server->get_next_command();
624 int l = command.length();
625 if (l > 0) {
626 std::istringstream is(command);
627 char c;
628 is >> c;
629 if (c == 'B') {
630 // button
631 char ud;
632 is >> ud;
633 int button;
634 is >> button;
635 bool down = ud == 'D';
636 uint8_t sendme = (down ? 0x80 : 0) | (button & 0xff);
637 // printf("button %d %s : sending char to mainboard: %02x\n", button, down ? "down" : "up", sendme);
638 xmit_char(sendme);
639 xmit_char(0x00);
640 } else if (c == 'A') {
641 // analog value from ES5505 OTIS: 10 bits, left-aligned within 16 bits.
642 int channel, value;
643 is >> channel;
644 is >> value;
645 uint16_t analog_value = (value << 6);
646 // printf("analog: channel %d, value %d = %04x\n", channel, value, analog_value);
647 set_analog_value(channel, analog_value);
648 }
649 }
650 }
651 }
652
set_analog_value(offs_t offset,uint16_t value)653 void esqpanel_device::set_analog_value(offs_t offset, uint16_t value)
654 {
655 m_write_analog(offset, value);
656 }
657
658 /* panel with 1x22 VFD display used in the EPS-16 and EPS-16 Plus */
659
device_add_mconfig(machine_config & config)660 void esqpanel1x22_device::device_add_mconfig(machine_config &config)
661 {
662 ESQ1X22(config, m_vfd, 60);
663 }
664
665
esqpanel1x22_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)666 esqpanel1x22_device::esqpanel1x22_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
667 esqpanel_device(mconfig, ESQPANEL1X22, tag, owner, clock),
668 m_vfd(*this, "vfd")
669 {
670 m_eps_mode = true;
671 }
672
673 /* panel with 2x40 VFD display used in the ESQ-1, SQ-80 */
674
device_add_mconfig(machine_config & config)675 void esqpanel2x40_device::device_add_mconfig(machine_config &config)
676 {
677 ESQ2X40(config, m_vfd, 60);
678 }
679
680
esqpanel2x40_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)681 esqpanel2x40_device::esqpanel2x40_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
682 esqpanel_device(mconfig, ESQPANEL2X40, tag, owner, clock),
683 m_vfd(*this, "vfd")
684 {
685 m_eps_mode = false;
686 }
687
688 /* panel with 2x40 VFD display used in the VFX, VFX-SD, SD-1 series */
689
device_add_mconfig(machine_config & config)690 void esqpanel2x40_vfx_device::device_add_mconfig(machine_config &config)
691 {
692 ESQ2X40(config, m_vfd, 60);
693 }
694
esqpanel2x40_vfx_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)695 esqpanel2x40_vfx_device::esqpanel2x40_vfx_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
696 esqpanel_device(mconfig, ESQPANEL2X40_VFX, tag, owner, clock),
697 m_vfd(*this, "vfd")
698 {
699 m_eps_mode = false;
700 }
701
write_contents(std::ostream & o)702 bool esqpanel2x40_vfx_device::write_contents(std::ostream &o)
703 {
704 m_vfd->write_contents(o);
705 for (int i = 0; i < m_light_states.size(); i++)
706 {
707 o.put(char(0xff));
708 o.put((m_light_states[i] << 6) | i);
709 }
710 return true;
711 }
712
713
714
715 // --- SQ1 - Parduz --------------------------------------------------------------------------------------------------------------------------
device_add_mconfig(machine_config & config)716 void esqpanel2x16_sq1_device::device_add_mconfig(machine_config &config)
717 {
718 ESQ2X16_SQ1(config, m_vfd, 60);
719 }
720
721 // --- SQ1 - Parduz --------------------------------------------------------------------------------------------------------------------------
esqpanel2x16_sq1_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)722 esqpanel2x16_sq1_device::esqpanel2x16_sq1_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
723 esqpanel_device(mconfig, ESQPANEL2X16_SQ1, tag, owner, clock),
724 m_vfd(*this, "vfd")
725 {
726 m_eps_mode = false;
727 }
728