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