2 * Copyright (C) 2023 Anton Blad
3 * Copyright (C) 2023 Fredrik Kuivinen
4 * Copyright (C) 2023 Jakob Rosén
6 * This file is licensed under GPL v2.
13 #include "ziparchive.h"
20 #include "trutproto.h"
23 // -d option: Debug level.
24 static int opt_debug
= 0;
26 enum TapTransferMode
{ TapIdle
, TapSending
, TapReceiving
, TapCancellingSending
};
28 // Specifies if a command was given on the command line. opt_singlecmd
29 // is 1 if a command was given on the command line.
30 static int opt_singlecmd
= 0;
31 static enum CmdCode opt_cmd
= CmdUnknown
; //Jakob
32 static const char* opt_cmdarg
= 0;
34 static enum TapTransferMode tap_mode
= TapIdle
;
35 static struct buffer
* data_buffer
= nullptr;
36 static bool crc_query_from_user
;
40 #include "bitcoach_routines.h"
42 static usbw_waitables
* waitables
;
44 void Engine::examine_msg(const char* data
, int size
)
46 int arglen
= size
- 1;
47 const char* args
= data
+ 1;
54 set_status("USB message error (motor), check console");
55 msg_error(data
, size
);
60 case TRUT_MSG_MOTOR_OFF
:
61 elapsed_timer
.pause();
63 set_status("Motor is off");
65 case TRUT_MSG_MOTOR_ON
:
66 set_status("Motor is on");
67 elapsed_timer
.start();
70 set_status("Motor in invalid state");
74 case TRUT_MSG_LOADFINISHED
:
75 fprintf(stderr
,"Load finished in %llu msec\n", elapsed_timer
.getTime());
76 set_status("Load finished");
79 // For now, do nothing
89 void Engine::process() {
97 ret
= trutusb_getmsg((uint8_t*) data
, sizeof(data
));
100 examine_msg(data
, ret
); // Added by Jakob for qload
103 printf("Error getting TRUT message\n");
106 if(trutusb_xferactive() && trutusb_xfercomplete())
110 if(tap_mode
== TapIdle
)
112 printf("Cleaning transfer in idle mode (should not happen).\n");
114 else if(tap_mode
== TapSending
)
116 printf("Finished sending tap.\n");
117 free_buffer(data_buffer
);
120 quit
=1; // Added by Jakob. Quit when finished!
122 else if(tap_mode
== TapReceiving
)
124 printf("Finished receiving tap.\n");
125 free_buffer(data_buffer
);
131 printf("Undefined mode while cleaning transfer.\n");
137 if(opt_singlecmd
> 0 && tap_mode
== TapIdle
)
142 start_tap_send(TRUT_MODE_LOAD
, opt_cmdarg
);
145 start_tap_receive(TRUT_MODE_DUMP
, opt_cmdarg
);
148 start_tap_send(TRUT_MODE_WRITE
, opt_cmdarg
);
151 start_tap_send(TRUT_MODE_SAVE
, opt_cmdarg
);
158 if(tap_mode
== TapIdle
) // an error occurred
161 if(opt_singlecmd
== 2)
167 if(tap_mode
== TapCancellingSending
)
169 printf("Tap send cancelled.\n");
170 set_status("Tape stopped");
171 // free_buffer(data_buffer);
176 waitables
= trutusb_getwaitables();
179 printf("Error getting list of USB waitables\n");
184 // ret = wait_for_stuff(cmd, sizeof(cmd), waitables, events);
186 // waitables is now global
187 // The other ones are not needed since we are not using stdin anymore
189 // Before waiting for stuff, let's see if it's time to quit
190 if (quit
!=0) emit
application_quit_signal();
196 trutusb_handleevents();
197 trutusb_freewaitables(waitables);
198 ...are handled by the wakeup(int) slot
203 void Engine::set_gui(Ui::MainWindow
* my_ui
) {
205 if (GUI_GHOST_BUTTONS
) {
206 gui_enable_load_panel();
207 gui_disable_play_panel();
211 void Engine::wait_for_stuff() {
214 fprintf(stderr
, "Sleeping with %d readfd and %d writefd...\n", waitables
->num_readobj
,
215 waitables
->num_writeobj
);
217 // We do this the Bitcoach/select way, so all socket notifiers are cleared each time an event occurs.
218 // This is not nice when using QSocketNotifiers. Instead, we should keep track of them and only add
219 // new / delete expired notifiers. I suppose :)
221 // Substitute for FD_ZERO(&readfd);
222 for (int i
=0; i
<notify_read
.size(); i
++) {
223 notify_read
[i
]->setEnabled(false); // Should be redundant
224 delete notify_read
[i
];
228 // No substitute for FD_SET(0, &readfd);
229 // Since we want to monitor stdin
231 // Substitute for FD_ZERO(&writefd);
232 for (int i
=0; i
<notify_write
.size(); i
++) {
233 notify_write
[i
]->setEnabled(false); // Should be redundant
234 delete notify_write
[i
];
236 notify_write
.clear();
238 struct timeval timeout
;
242 for(i
= 0; i
< waitables
->num_readobj
; i
++)
244 fd
= waitables
->readobj
[i
];
245 QSocketNotifier
* qsn
=new QSocketNotifier(fd
,QSocketNotifier::Read
);
246 QObject::connect(qsn
, SIGNAL(activated(int)), this, SLOT(usb_event(int)));
247 qsn
->setEnabled(true);
248 notify_read
.push_back(qsn
);
251 for(i
= 0; i
< waitables
->num_writeobj
; i
++)
253 fd
= waitables
->writeobj
[i
];
254 QSocketNotifier
* qsn
=new QSocketNotifier(fd
,QSocketNotifier::Write
);
255 QObject::connect(qsn
, SIGNAL(activated(int)), this, SLOT(usb_event(int)));
256 qsn
->setEnabled(true);
257 notify_write
.push_back(qsn
);
260 if(waitables
->timeout_pending
== 1)
263 fprintf(stderr
, "timeout %d sec %d us\n", (int)waitables
->timeout
.tv_sec
,
264 (int)waitables
->timeout
.tv_usec
);
265 // ret = select(max_fd + 1, &readfd, &writefd, NULL, &timeout);
266 timeout
= waitables
->timeout
;
267 timeout_timer
.start((int)timeout
.tv_sec
*1000+ceil((double)timeout
.tv_usec
/1000.0));
273 usbw_debug(opt_debug
);
276 if(trutusb_isattached())
278 printf("TRUT connected! Get ready to rambo!\n");
279 set_status("TRUT connected! Get ready to rambo!");
283 printf("Found no TRUT :(. You must always connect TRUT! :(\n");
284 set_status("Found no TRUT :(. You must always connect TRUT! :(");
290 printf("Can not claim TRUT :(\n");
291 set_status("Can not claim TRUT :(");
295 if(trutusb_setmsg(true))
297 printf("Failed to enable messages\n");
298 set_status("Failed to enable messages");
301 init_timeout_timer();
307 void Engine::init_timeout_timer() {
308 timeout_timer
.setSingleShot(true); // We don't want a periodic timer
309 connect(&timeout_timer
, SIGNAL(timeout()), this, SLOT(timeout_event()));
312 // Called after an usb_event(int) or a timeout_event()
313 // This is the code that succeeds wait_for_stuff()
314 void Engine::wakeup() {
315 timeout_timer
.stop(); // If an event was catched before a timeout, disable the timer
316 trutusb_freewaitables(waitables
);
320 // Slot called by QSocketNotifier::activate(int)
321 void Engine::usb_event(int fid
) {
322 if (DEBUG
>0) std::cout
<< "Processing USB event from descriptor " << fid
<< std::endl
;
323 trutusb_handleevents();
327 // Slot called by QTimer::timeout()
328 void Engine::timeout_event() {
329 std::cout
<< "Timeout timeout occured!" << std::endl
;
330 assert(!timeout_timer
.isActive());
334 void Engine::start() {
338 void Engine::shut_down() {
339 if(trutusb_setmsg(false))
340 printf("Failed to disable messages\n");
342 if(trutusb_isattached())
345 fprintf(stderr
, "Good bye\n");
348 void Engine::print_message(std::string str
) {
349 printf("%s\n", str
.c_str());
352 QString
Engine::get_file_name(QString str
) {
353 str
=str
.replace("\\", "/");
354 int index
=str
.lastIndexOf("/");
355 return str
.last(str
.length()-index
-1);
358 void Engine::load(QString filename
) {
359 elapsed_timer
.init();
363 if (filename
.endsWith(".zip")) {
364 printf("Engine: Zip file detected\n");
366 } else if (filename
.endsWith(".tap")) {
367 printf("Engine: Tap file detected\n");
369 fprintf(stderr
, "Engine: Error! No valid file was selected\n");
372 active_filename
=filename
;
373 //set_status("Loaded "+get_file_name(active_filename));
375 ui
->fileBox
->blockSignals(true);
376 ui
->fileBox
->clear();
379 ZipArchive
zip(filename
.toStdString());
380 if (!zip
.isZipFile()) {
381 fprintf(stderr
,"ERROR! Not a zip file\n");
384 std::vector
<std::string
> strvec
=zip
.getFilenames(".tap");
386 for (int i
=0; i
<(int)strvec
.size(); i
++) {
387 ui
->fileBox
->addItem(QString::fromStdString(strvec
.at(i
)));
391 ui
->fileBox
->addItem(get_file_name(filename
));
394 if (ui
->fileBox
->count()<=1) {
395 ui
->fileBox
->setEnabled(false);
397 ui
->fileBox
->setEnabled(true);
399 ui
->fileBox
->blockSignals(false);
401 gui_disable_play_panel();
403 // Ok, the combobox is setup at this point
405 if (data_buffer
!= nullptr) free_buffer(data_buffer
);
407 if (is_zip
&& ui
->fileBox
->count()==0) {
408 printf("No valid files in Zip archive\n");
412 struct tape_data
* tap
;
414 // If it's a zip file, the item is loaded via on_fileBox_currentIndexChanged()
415 // that is triggered automatically
417 // Load tap file into buffer
418 tap
=load_tap_file(filename
.toStdString().c_str());
419 data_buffer
= jakob_internal(tap
);
421 set_status("Loaded TAP file: "+ui
->fileBox
->currentText());
423 // Load the first tap file in the zip archive
426 set_status("PRESS PLAY ON TAPE");
427 gui_enable_play_panel();
430 void Engine::change_zip_item() {
431 assert(!active_filename
.isEmpty());
432 if (active_filename
.endsWith(".zip")) {
433 struct tape_data
* tap
;
434 QString selected_item
=ui
->fileBox
->currentText();
435 tap
=load_tap_in_zip(active_filename
, selected_item
);
436 data_buffer
= jakob_internal(tap
);
438 set_status("Loaded from ZIP archive: "+ui
->fileBox
->currentText());
439 elapsed_timer
.init();
444 struct tape_data
* Engine::load_tap_in_zip(QString filename
, QString itemname
) {
445 //struct buffer* buf;
446 //buf = (struct buffer*)xmalloc(sizeof(struct buffer));
447 struct tape_data
* tap
;
448 struct ZipArchive::zip_buffer zipbuf
;
450 ZipArchive
zip(filename
.toStdString());
451 zip
.extractItem(itemname
.toStdString(), &zipbuf
);
453 buf
.data
=zipbuf
.data
;
454 buf
.length
=(int)zipbuf
.length
;
456 tap
=decode_tap(&buf
);
461 void Engine::play() {
463 if(tap_mode
!= TapIdle
)
465 fprintf(stderr
,"TRUT64 is not in idle mode.\n");
466 set_status("TRUT64 is working. Do not disturb.");
470 assert(data_buffer
!= nullptr); // Should not happen
472 fprintf(stderr
,"Buffer time: %llu\n", calculate_buffer_time(data_buffer
));
474 start_refresh_timer();
476 //uint64_t time_position_msec=elapsed_timer.getTime();
478 uint64_t time_position_msec
=(uint64_t)ui
->lcdNumber
->intValue()*1000;
480 int buffer_index
=calculate_buffer_index(data_buffer
, time_position_msec
*1000);
481 fprintf(stderr
, "buffer_index: %d\n", buffer_index
);
482 if(trutusb_xferinit(TRUT_MODE_LOAD
, data_buffer
->data
+buffer_index
, data_buffer
->length
-buffer_index
) == 0)
484 printf("Initialized data transfer of %d bytes\n", data_buffer
->length
);
485 tap_mode
= TapSending
;
486 gui_disable_load_panel();
490 printf("Failed to initialize data transfer\n");
491 free_buffer(data_buffer
);
492 gui_disable_play_panel();
496 void Engine::gui_enable_load_panel() {
497 ui
->initButton
->setEnabled(true);
498 ui
->loadButton
->setEnabled(true);
499 ui
->setButton
->setEnabled(true);
500 ui
->remButton
->setEnabled(true);
501 ui
->saveButton
->setEnabled(true);
502 ui
->dumpButton
->setEnabled(true);
503 ui
->fileBox
->setEnabled(true);
506 void Engine::gui_disable_load_panel() {
507 ui
->initButton
->setEnabled(false);
508 ui
->loadButton
->setEnabled(false);
509 ui
->setButton
->setEnabled(false);
510 ui
->remButton
->setEnabled(false);
511 ui
->saveButton
->setEnabled(false);
512 ui
->dumpButton
->setEnabled(false);
513 ui
->fileBox
->setEnabled(false);
516 void Engine::gui_enable_play_panel() {
517 ui
->playButton
->setEnabled(true);
518 ui
->stopButton
->setEnabled(true);
521 void Engine::gui_disable_play_panel() {
522 ui
->playButton
->setEnabled(false);
523 ui
->stopButton
->setEnabled(false);
527 void Engine::start_refresh_timer() {
528 refresh_timer
.setSingleShot(false);
529 connect(&refresh_timer
, SIGNAL(timeout()), this, SLOT(refresh_event()));
530 refresh_timer
.setInterval(500);
531 refresh_timer
.start();
534 void Engine::refresh_event() {
535 ui
->lcdNumber
->display((int)elapsed_timer
.getTime()/1000);
538 int Engine::calculate_buffer_index(struct buffer
* buf
, uint64_t time_position_usec
) {
539 struct buffer_position result
;
540 calculate_internal_buffer_position(&result
, buf
, time_position_usec
);
544 uint64_t Engine::calculate_buffer_time(struct buffer
* buf
) {
545 struct buffer_position result
;
546 uint64_t time_position
=std::numeric_limits
<uint64_t>::max();
547 calculate_internal_buffer_position(&result
, buf
, time_position
);
548 return result
.elapsed_time
;
551 bool Engine::calculate_internal_buffer_position(struct buffer_position
* result
, struct buffer
* buf
, uint64_t time_position_usec
) {
552 uint64_t elapsed_time
=0;
556 assert((1.0*AVR_CLK_MHZ
/AVR_PRESCALING
)==1.0);
557 time_position_usec
=time_position_usec
*(1.0*AVR_CLK_MHZ
/AVR_PRESCALING
);
560 elapsed_time
+=current_pulse
;
562 current_pulse
=buf
->data
[i
++] << 8;
563 current_pulse
+=buf
->data
[i
++];
564 if (current_pulse
==0) {
568 current_pulse
=buf
->data
[i
++] << 8;
569 current_pulse
+=buf
->data
[i
++];
570 } while (current_pulse
==0);
571 current_pulse
+=overflows
*0xffff;
574 } while (elapsed_time
+current_pulse
<time_position_usec
&& i
<buf
->length
);
575 if (i
==buf
->length
) {
576 result
->index
=buf
->length
;
577 result
->elapsed_time
=elapsed_time
+current_pulse
;
578 return false; // We did not find time_position (but measured the total buffer time)
580 result
->index
=bufpos
;
581 result
->elapsed_time
=elapsed_time
;
585 // Used for debugging purposes. Does not take extension of short pulses
586 // (that is performed when converting to jakob_internal) into account.
587 uint64_t Engine::calculate_tape_data_time(struct tape_data
* buf
) {
588 uint64_t elapsed_time
=0;
589 for (int i
=0; i
<buf
->length
; i
++) {
590 elapsed_time
+=floor(0.5+(1.0*buf
->data
[i
]/C64_CLK_MHZ
));
595 void Engine::stop() {
597 if(tap_mode
== TapIdle
)
599 printf("No data transfer in progress.\n");
601 else if(tap_mode
== TapSending
)
603 if(trutusb_xferactive())
606 printf("Cancelled sending tap, wait until completion..\n");
607 tap_mode
= TapCancellingSending
;
611 printf("Already finished?!\n");
616 printf("Later...\n");
618 elapsed_timer
.pause();
620 gui_enable_load_panel();
623 void Engine::set_status(QString str
) {
624 ui
->plainTextEdit
->appendPlainText(str
);
627 void Engine::set_counter(int counter_sec
) {
628 elapsed_timer
.init();
629 elapsed_timer
.setOffsetSeconds(counter_sec
);
633 void Engine::refresh_counter() {