A i8086 based IBM PC emulator (runs FreeDOS, MS-DOS, CPM86, Linux-ELK, Windows 3.0)
#pragma message "This sketch requires Tools->Partition Scheme = Huge APP"
#include <memory>
#include "esp32-hal-psram.h"
extern "C" {
#include "esp_spiram.h"
}
#include "esp_sntp.h"
#include <Preferences.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include "mconf.h"
#include "machine.h"
using std::unique_ptr;
using fabgl::StringList;
using fabgl::imin;
using fabgl::imax;
Preferences preferences;
InputBox ibox;
Machine * machine;
__NOINIT_ATTR static timeval savedTimeValue;
static bool wifiConnected = false;
static bool downloadOK = false;
bool tryToConnect()
{
bool connected = WiFi.status() == WL_CONNECTED;
if (!connected) {
char SSID[32] = "";
char psw[32] = "";
if (preferences.getString("SSID", SSID, sizeof(SSID)) && preferences.getString("WiFiPsw", psw, sizeof(psw))) {
ibox.progressBox("", "Abort", true, 200, [&](fabgl::ProgressForm * form) {
WiFi.begin(SSID, psw);
for (int i = 0; i < 32 && WiFi.status() != WL_CONNECTED; ++i) {
if (!form->update(i * 100 / 32, "Connecting to %s...", SSID))
break;
delay(500);
if (i == 16)
WiFi.reconnect();
}
connected = (WiFi.status() == WL_CONNECTED);
});
if (!connected) {
WiFi.disconnect();
ibox.message("", "WiFi Connection failed!");
}
}
}
return connected;
}
bool checkWiFi()
{
wifiConnected = tryToConnect();
if (!wifiConnected) {
if (ibox.message("WiFi Configuration", "Configure WiFi?", "No", "Yes") == InputResult::Enter) {
do {
int networksCount = 0;
ibox.progressBox("", nullptr, false, 200, [&](fabgl::ProgressForm * form) {
form->update(0, "Scanning WiFi networks...");
networksCount = WiFi.scanNetworks();
});
if (networksCount > 0) {
StringList list;
for (int i = 0; i < networksCount; ++i)
list.appendFmt("%s (%d dBm)", WiFi.SSID(i).c_str(), WiFi.RSSI(i));
int s = ibox.menu("WiFi Configuration", "Please select a WiFi network", &list);
if (s > -1) {
char psw[32] = "";
if (ibox.textInput("WiFi Configuration", "Insert WiFi password", psw, 31, "Cancel", "OK", true) == InputResult::Enter) {
preferences.putString("SSID", WiFi.SSID(s).c_str());
preferences.putString("WiFiPsw", psw);
wifiConnected = tryToConnect();
if (wifiConnected)
ibox.message("", "Connection succeeded!");
} else
break;
} else
break;
} else {
ibox.message("", "No WiFi network found!");
break;
}
WiFi.scanDelete();
} while (!wifiConnected);
}
}
return wifiConnected;
}
void shutdownHandler()
{
gettimeofday(&savedTimeValue, nullptr);
}
void updateDateTime()
{
setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1);
tzset();
if (esp_reset_reason() == ESP_RST_SW) {
savedTimeValue.tv_usec += (int) esp_timer_get_time();
savedTimeValue.tv_sec += savedTimeValue.tv_usec / 1000000;
savedTimeValue.tv_usec %= 1000000;
settimeofday(&savedTimeValue, nullptr);
return;
}
if (checkWiFi()) {
ibox.progressBox("", nullptr, true, 200, [&](fabgl::ProgressForm * form) {
sntp_setoperatingmode(SNTP_OPMODE_POLL);
sntp_setservername(0, (char*)"pool.ntp.org");
sntp_init();
for (int i = 0; i < 12 && sntp_get_sync_status() != SNTP_SYNC_STATUS_COMPLETED; ++i) {
form->update(i * 100 / 12, "Getting date-time from SNTP...");
delay(500);
}
sntp_stop();
ibox.setAutoOK(2);
ibox.message("", "Date and Time updated. Restarting...");
esp_restart();
});
} else {
auto tm = (struct tm){ .tm_sec = 0, .tm_min = 0, .tm_hour = 8, .tm_mday = 14, .tm_mon = 7, .tm_year = 84 };
auto now = (timeval){ .tv_sec = mktime(&tm) };
settimeofday(&now, nullptr);
}
}
bool downloadURL(char const * URL, FILE * file)
{
downloadOK = false;
char const * filename = strrchr(URL, '/') + 1;
ibox.progressBox("", "Abort", true, 380, [&](fabgl::ProgressForm * form) {
form->update(0, "Preparing to download %s", filename);
HTTPClient http;
http.begin(URL);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
if (file) {
int tlen = http.getSize();
int len = tlen;
auto buf = (uint8_t*) SOC_EXTRAM_DATA_LOW;
WiFiClient * stream = http.getStreamPtr();
int dsize = 0;
while (http.connected() && (len > 0 || len == -1)) {
size_t size = stream->available();
if (size) {
int c = stream->readBytes(buf, size);
auto wr = fwrite(buf, 1, c, file);
if (wr != c) {
dsize = 0;
break;
}
dsize += c;
if (len > 0)
len -= c;
if (!form->update((int64_t)dsize * 100 / tlen, "Downloading %s (%.2f / %.2f MB)", filename, (double)dsize / 1048576.0, tlen / 1048576.0))
break;
}
}
downloadOK = (len == 0 || (len == -1 && dsize > 0));
}
}
http.end();
});
return downloadOK;
}
char const * getDisk(char const * url)
{
FileBrowser fb(SD_MOUNT_PATH);
char const * filename = nullptr;
if (url) {
if (strncmp("://", url + 4, 3) == 0) {
filename = strrchr(url, '/') + 1;
if (filename && !fb.exists(filename, false)) {
if (!checkWiFi())
return nullptr;
auto file = fb.openFile(filename, "wb");
bool success = downloadURL(url, file);
fclose(file);
if (!success) {
fb.remove(filename);
return nullptr;
}
}
} else {
if (fb.filePathExists(url))
filename = url;
}
}
return filename;
}
void sysReqCallback()
{
machine->graphicsAdapter()->enableVideo(false);
int s = ibox.menu("", "Select a command", "Restart (Boot Menu);Continue;Mount Disk");
switch (s) {
case 0:
esp_restart();
break;
case 2:
{
int s = ibox.menu("", "Select Drive", "Floppy A (fd0);Floppy B (fd1)");
if (s > -1) {
constexpr int MAXNAMELEN = 256;
unique_ptr<char[]> dir(new char[MAXNAMELEN + 1] { '/', 'S', 'D', 0 } );
unique_ptr<char[]> filename(new char[MAXNAMELEN + 1] { 0 } );
if (machine->diskFilename(s))
strcpy(filename.get(), machine->diskFilename(s));
if (ibox.fileSelector("Select Disk Image", "Image Filename", dir.get(), MAXNAMELEN, filename.get(), MAXNAMELEN) == InputResult::Enter) {
machine->setDriveImage(s, filename.get());
}
}
break;
}
default:
break;
}
ibox.end();
PS2Controller::keyboard()->enableVirtualKeys(false, false);
machine->graphicsAdapter()->enableVideo(true);
}
void setup()
{
Serial.begin(115200); delay(500); printf("\n\n\nReset\n\n");
disableCore0WDT();
delay(100);
disableCore1WDT();
preferences.begin("PCEmulator", false);
ibox.setBackgroundColor(RGB888(0, 0, 0));
ibox.onPaint = [&](Canvas * canvas) { drawInfo(canvas); };
#ifdef BOARD_HAS_PSRAM
ibox.message("Warning!", "Please disable PSRAM to improve performance!");
#endif
if (esp_spiram_init() != ESP_OK)
ibox.message("Error!", "This app requires a board with PSRAM!", nullptr, nullptr);
#ifndef BOARD_HAS_PSRAM
esp_spiram_init_cache();
#endif
if (!FileBrowser::mountSDCard(false, SD_MOUNT_PATH, 8))
ibox.message("Error!", "This app requires a SD-CARD!", nullptr, nullptr);
esp_register_shutdown_handler(shutdownHandler);
updateDateTime();
MachineConf mconf;
ibox.setAutoOK(6);
int idx = preferences.getInt("dconf", 0);
for (bool showDialog = true; showDialog; ) {
loadMachineConfiguration(&mconf);
StringList dconfs;
for (auto conf = mconf.getFirstItem(); conf; conf = conf->next)
dconfs.append(conf->desc);
dconfs.select(idx, true);
ibox.setupButton(0, "Browse Files");
ibox.setupButton(1, "Options", "Edit;New;Remove", 52);
auto r = ibox.select("Machine Configurations", "Please select a machine configuration", &dconfs, nullptr, "Run");
idx = dconfs.getFirstSelected();
switch (r) {
case InputResult::ButtonExt0:
ibox.folderBrowser("Browse Files", SD_MOUNT_PATH);
break;
case InputResult::ButtonExt1:
switch (ibox.selectedSubItem()) {
case 0:
editConfigDialog(&ibox, &mconf, idx);
break;
case 1:
newConfigDialog(&ibox, &mconf, idx);
break;
case 2:
delConfigDialog(&ibox, &mconf, idx);
break;
};
break;
case InputResult::Enter:
showDialog = false;
break;
default:
break;
}
ibox.setAutoOK(0);
}
idx = imax(idx, 0);
preferences.putInt("dconf", idx);
auto conf = mconf.getItem(idx);
char const * diskFilename[DISKCOUNT];
downloadOK = true;
for (int i = 0; i < DISKCOUNT && downloadOK; ++i)
diskFilename[i] = getDisk(conf->disk[i]);
if (!downloadOK || (!diskFilename[0] && !diskFilename[2])) {
ibox.message("Error!", "Unable to get system disks!");
esp_restart();
}
if (wifiConnected) {
ibox.setAutoOK(2);
ibox.message("", "Disks downloaded. Restarting...");
esp_restart();
}
ibox.end();
machine = new Machine;
machine->setBaseDirectory(SD_MOUNT_PATH);
for (int i = 0; i < DISKCOUNT; ++i)
machine->setDriveImage(i, diskFilename[i], conf->cylinders[i], conf->heads[i], conf->sectors[i]);
machine->setBootDrive(conf->bootDrive);
machine->setSysReqCallback(sysReqCallback);
machine->run();
}
#if FABGLIB_VGAXCONTROLLER_PERFORMANCE_CHECK
namespace fabgl {
extern volatile uint64_t s_vgapalctrlcycles;
}
using fabgl::s_vgapalctrlcycles;
#endif
void loop()
{
#if FABGLIB_VGAXCONTROLLER_PERFORMANCE_CHECK
static uint32_t tcpu = 0, s1 = 0, count = 0;
tcpu = machine->ticksCounter();
s_vgapalctrlcycles = 0;
s1 = fabgl::getCycleCount();
delay(1000);
printf("%d\tCPU: %d", count, machine->ticksCounter() - tcpu);
printf(" Graph: %lld / %d (%d%%)\n", s_vgapalctrlcycles, fabgl::getCycleCount() - s1, (int)((double)s_vgapalctrlcycles/240000000*100));
++count;
#else
vTaskDelete(NULL);
#endif
}
static int queueSize
Size of display controller primitives queue.
This file is the all in one include file. Application can just include this file to use FabGL library...