diff --git a/README.md b/README.md
index f02797ec..73665387 100644
--- a/README.md
+++ b/README.md
@@ -4,11 +4,8 @@ FluxEngine
(If you're reading this on GitHub, the formatting's a bit messed up. [Try the
version on cowlark.com instead.](http://cowlark.com/fluxengine/))
-**Breaking news!** As of 2022-09-09, there's new [filesystem
-support](doc/filesystem.md). Read (and sometimes write) files directly from
-(and to) your disks, with eight different file systems! It works in the GUI,
-too, which is available for Linux (and other Unix clones), Windows and OSX. See
-the details below.
+**Breaking news!** As of 2024-10-01, the FluxEngine client software works
+(to a point) with [Applesauce](doc/applesauce.md) hardware.
@@ -35,12 +32,14 @@ Don't believe me? Watch the demo reel!
**New!** The FluxEngine client software now works with
-[Greaseweazle](https://github.com/keirf/Greaseweazle/wiki) hardware. So, if you
-can't find a PSoC5 development kit, or don't want to use the Cypress Windows
-tools for programming it, you can use one of these instead. Very nearly all
-FluxEngine features are available with the Greaseweazle and it works out-of-the
-box. See the [dedicated Greaseweazle documentation page](doc/greaseweazle.md)
-for more information.
+[Greaseweazle](https://github.com/keirf/Greaseweazle/wiki) and
+[Applesauce](https://applesaucefdc.com/) hardware. So, if you can't find a PSoC5
+development kit, or don't want to use the Cypress Windows tools for programming
+it, you can use one of these instead. Very nearly all FluxEngine features are
+available with the Greaseweazle and it works out-of-the box; the Applesauce is a
+bit less supported but still works. See the [dedicated Greaseweazle
+documentation page](doc/greaseweazle.md) or the [Applesauce
+page](doc/applesauce.md) for more information.
Where?
------
diff --git a/build.py b/build.py
index ed97ddec..309d2c28 100644
--- a/build.py
+++ b/build.py
@@ -90,6 +90,8 @@
"./lib/proto.cc",
"./lib/readerwriter.cc",
"./lib/sector.cc",
+ "./lib/usb/applesauce.cc",
+ "./lib/usb/applesauceusb.cc",
"./lib/usb/fluxengineusb.cc",
"./lib/usb/greaseweazle.cc",
"./lib/usb/greaseweazleusb.cc",
@@ -209,6 +211,7 @@
"lib/proto.h": "./lib/proto.h",
"lib/readerwriter.h": "./lib/readerwriter.h",
"lib/sector.h": "./lib/sector.h",
+ "lib/usb/applesauce.h": "./lib/usb/applesauce.h",
"lib/usb/greaseweazle.h": "./lib/usb/greaseweazle.h",
"lib/usb/usb.h": "./lib/usb/usb.h",
"lib/usb/usbfinder.h": "./lib/usb/usbfinder.h",
diff --git a/doc/applesauce.md b/doc/applesauce.md
new file mode 100644
index 00000000..3f7b55a7
--- /dev/null
+++ b/doc/applesauce.md
@@ -0,0 +1,72 @@
+Using the FluxEngine client software with Applesauce hardware
+===============================================================
+
+The FluxEngine isn't the only project which does this; another one is the
+[Applesauce](https://applesaucefdc.com/), a proprietary but feature-rich
+off-the-shelf imaging device. Its native client (which is a lot better than
+FluxEngine) only works on OSX, so if you want to use it anywhere else,
+the FluxEngine client works.
+
+The Applesauce works rather differently to the FluxEngine hardware or the
+[Greaseweazle](greaseweazle.md), so there are some caveats.
+
+ - Rather than streaming the flux data from the device to the PC, the Applesauce
+ has a fixed buffer in RAM used to capture a complete image of a track. This is
+ then downloaded later. The advantage is that USB bandwidth isn't an issue; the
+ downside is that the buffer can only hold so much data. In fact, the Applesauce
+ can only capture 1.25 revolutions or 2.25 revolutions, nothing else. When used
+ with the FluxEngine the capture time will be ignored apart from used to
+ determine whether you want a 'long' or 'short' capture.
+
+ - The current (v2) firmware only supports reading, not writing (via clients
+ other than the official one, of course). The new (v3) firmware will support
+ writing, but it's not out yet, so for the time being the FluxEngine client is
+ read only.
+
+ - You can only do synchronous reads, i.e., reads starting from the index mark.
+
+Other than this, the FluxEngine software supports the Applesauce almost
+out-of-the-box --- just plug it in and nearly everything should work. The
+FluxEngine software will autodetect it. If you have more than one device plugged
+in, use `--usb.serial=` to specify which one you want to use.
+
+I am aware that having _software_ called FluxEngine and _hardware_ called
+FluxEngine makes things complicated when you're not using the FluxEngine client
+software with a FluxEngine board, but I'm afraid it's too late to change that
+now. Sorry.
+
+What works
+----------
+
+Supported features with the Greaseweazle include:
+
+ - simple reading of disks, seeking etc
+ - erasing disks
+ - hard sectored disks
+ - determining disk rotation speed
+ - normal IBM buses
+
+I don't know what happens if you try to use an Apple Superdrive or a Apple II
+disk with FluxEngine. If you've got one, [please get in
+touch](https://github.com/davidgiven/fluxengine/issues/new)!
+
+What doesn't work
+-----------------
+
+ - voltage measurement
+ - writing
+
+Who to contact
+--------------
+
+I want to make it clear that the FluxEngine code is _not_ supported by the
+Applesauce team. If you have any problems, please [contact
+me](https://github.com/davidgiven/fluxengine/issues/new) and not them.
+
+In addition, the Applesauce release cycle is not synchronised to the
+FluxEngine release cycle, so it's possible you'll have a version of the
+Applesauce firmware which is not supported by FluxEngine. Hopefully, it'll
+detect this and complain. Again, [file an
+issue](https://github.com/davidgiven/fluxengine/issues/new) and I'll look into
+it.
+
diff --git a/doc/using.md b/doc/using.md
index 6008cec9..646b8676 100644
--- a/doc/using.md
+++ b/doc/using.md
@@ -50,9 +50,9 @@ file while changing the decoder options, to save disk wear. It's also much faste
### Connecting it up
-To use, simply plug your FluxEngine (or [Greaseweazle](greaseweazle.md)) into
-your computer and run the client. If a single device is plugged in, it will be
-automatically detected and used.
+To use, simply plug your FluxEngine (or [Greaseweazle](greaseweazle.md) or
+[Applesauce](applesauce.md)) into your computer and run the client. If a single
+device is plugged in, it will be automatically detected and used.
If _more_ than one device is plugged in, you need to specify which one to use
with the `--usb.serial` parameter, which takes the device serial number as a
diff --git a/lib/decoders/fluxmapreader.cc b/lib/decoders/fluxmapreader.cc
index 4ce8f7c8..3fbfb89c 100644
--- a/lib/decoders/fluxmapreader.cc
+++ b/lib/decoders/fluxmapreader.cc
@@ -47,7 +47,7 @@ bool FluxmapReader::findEvent(int event, unsigned& ticks)
{
ticks = 0;
- for (;;)
+ while (!eof())
{
unsigned thisTicks;
int thisEvent;
@@ -57,11 +57,11 @@ bool FluxmapReader::findEvent(int event, unsigned& ticks)
if (thisEvent == F_EOF)
return false;
- if (eof())
- return false;
if ((event == thisEvent) || (event & thisEvent))
return true;
}
+
+ return false;
}
unsigned FluxmapReader::readInterval(nanoseconds_t clock)
diff --git a/lib/flags.cc b/lib/flags.cc
index 6f3a543f..a83ccdb3 100644
--- a/lib/flags.cc
+++ b/lib/flags.cc
@@ -156,7 +156,7 @@ std::vector FlagGroup::parseFlagsWithFilenames(int argc,
globalConfig().applyOption(path);
}
else
- error("unrecognised flag; try --help");
+ error("unrecognised flag '-{}'; try --help", key);
}
else
{
diff --git a/lib/fluxsource/fluxsource.proto b/lib/fluxsource/fluxsource.proto
index 7564717c..ab62bb65 100644
--- a/lib/fluxsource/fluxsource.proto
+++ b/lib/fluxsource/fluxsource.proto
@@ -6,7 +6,7 @@ message HardwareFluxSourceProto {}
message TestPatternFluxSourceProto {
optional double interval_us = 1 [default = 4.0, (help) = "interval between pulses"];
- optional double sequence_length_us = 2 [default = 200.0, (help) = "length of test sequence"];
+ optional double sequence_length_ms = 2 [default = 166.0, (help) = "length of test sequence"];
}
message EraseFluxSourceProto {}
diff --git a/lib/fluxsource/testpatternfluxsource.cc b/lib/fluxsource/testpatternfluxsource.cc
index b5a85c45..3b7cce88 100644
--- a/lib/fluxsource/testpatternfluxsource.cc
+++ b/lib/fluxsource/testpatternfluxsource.cc
@@ -18,9 +18,9 @@ class TestPatternFluxSource : public TrivialFluxSource
{
auto fluxmap = std::make_unique();
- while (fluxmap->duration() < (_config.sequence_length_us() * 1000000.0))
+ while (fluxmap->duration() < (_config.sequence_length_ms() * 1e6))
{
- fluxmap->appendInterval(_config.interval_us());
+ fluxmap->appendInterval(_config.interval_us() * TICKS_PER_US);
fluxmap->appendPulse();
}
diff --git a/lib/globals.cc b/lib/globals.cc
index bd28af65..85749805 100644
--- a/lib/globals.cc
+++ b/lib/globals.cc
@@ -14,5 +14,4 @@ double getCurrentTime(void)
void warning(const std::string msg)
{
log(msg);
- fmt::print("Warning: {}\n", msg);
}
diff --git a/lib/usb/applesauce.cc b/lib/usb/applesauce.cc
new file mode 100644
index 00000000..0bcce8c4
--- /dev/null
+++ b/lib/usb/applesauce.cc
@@ -0,0 +1,64 @@
+#include "lib/globals.h"
+#include "usb.h"
+#include "protocol.h"
+#include "lib/bytes.h"
+#include "greaseweazle.h"
+#include "lib/fluxmap.h"
+#include "lib/decoders/fluxmapreader.h"
+#include "lib/a2r.h"
+
+static double a2r_to_ticks(double a2rticks)
+{
+ return a2rticks * A2R_NS_PER_TICK / NS_PER_TICK;
+}
+
+static double ticks_to_a2r(double flticks)
+{
+ return flticks * NS_PER_TICK / A2R_NS_PER_TICK;
+}
+
+Bytes fluxEngineToApplesauce(const Bytes& fldata)
+{
+ Fluxmap fluxmap(fldata);
+ FluxmapReader fmr(fluxmap);
+ Bytes asdata;
+ ByteWriter bw(asdata);
+
+ while (!fmr.eof())
+ {
+ unsigned ticks;
+ if (!fmr.findEvent(F_BIT_PULSE, ticks))
+ break;
+
+ uint32_t applesauceTicks = ticks_to_a2r(ticks);
+ while (applesauceTicks >= 255)
+ {
+ bw.write_8(255);
+ applesauceTicks -= 255;
+ }
+ bw.write_8(applesauceTicks);
+ }
+
+ return asdata;
+}
+
+Bytes applesauceToFluxEngine(const Bytes& asdata)
+{
+ ByteReader br(asdata);
+ Fluxmap fluxmap;
+
+ unsigned a2rTicksSinceLastPulse = 0;
+ while (!br.eof())
+ {
+ uint8_t b = br.read_8();
+ a2rTicksSinceLastPulse += b;
+ if (b != 255)
+ {
+ fluxmap.appendInterval(a2r_to_ticks(a2rTicksSinceLastPulse));
+ fluxmap.appendPulse();
+ a2rTicksSinceLastPulse = 0;
+ }
+ }
+
+ return fluxmap.rawBytes();
+}
diff --git a/lib/usb/applesauce.h b/lib/usb/applesauce.h
new file mode 100644
index 00000000..2cef0151
--- /dev/null
+++ b/lib/usb/applesauce.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#define APPLESAUCE_VID 0x16c0
+#define APPLESAUCE_PID 0x0483
+
+#define APPLESAUCE_ID ((APPLESAUCE_VID << 16) | APPLESAUCE_PID)
+
+extern Bytes applesauceToFluxEngine(const Bytes& asdata);
+extern Bytes fluxEngineToApplesauce(const Bytes& fldata);
diff --git a/lib/usb/applesauceusb.cc b/lib/usb/applesauceusb.cc
new file mode 100644
index 00000000..5c8cf897
--- /dev/null
+++ b/lib/usb/applesauceusb.cc
@@ -0,0 +1,345 @@
+#include "lib/globals.h"
+#include "protocol.h"
+#include "lib/fluxmap.h"
+#include "lib/bytes.h"
+#include "lib/usb/usb.pb.h"
+#include "lib/utils.h"
+#include "applesauce.h"
+#include "serial.h"
+#include "usb.h"
+#include "lib/decoders/fluxmapreader.h"
+#include
+
+class ApplesauceUsb;
+static ApplesauceUsb* instance;
+
+static uint32_t ss_rand_next(uint32_t x)
+{
+ return (x & 1) ? (x >> 1) ^ 0x80000062 : x >> 1;
+}
+
+static Bytes applesauceReadDataToFluxEngine(const Bytes& asdata,
+ nanoseconds_t clock,
+ const std::vector& indexMarks)
+{
+ ByteReader br(asdata);
+ Fluxmap fluxmap;
+ auto indexIt = indexMarks.begin();
+ fluxmap.appendIndex();
+
+ unsigned totalTicks = 0;
+ while (!br.eof())
+ {
+ uint8_t b = br.read_8();
+ fluxmap.appendInterval(b * clock / NS_PER_TICK);
+ if (b != 255)
+ fluxmap.appendPulse();
+
+ totalTicks += b;
+ if ((indexIt != indexMarks.end()) && (totalTicks > *indexIt))
+ {
+ fluxmap.appendIndex();
+ indexIt++;
+ }
+ }
+
+ return fluxmap.rawBytes();
+}
+
+static Bytes fluxEngineToApplesauceWriteData(const Bytes& fldata)
+{
+ Fluxmap fluxmap(fldata);
+ FluxmapReader fmr(fluxmap);
+ Bytes asdata;
+ ByteWriter bw(asdata);
+
+ while (!fmr.eof())
+ {
+ unsigned ticks;
+ if (!fmr.findEvent(F_BIT_PULSE, ticks))
+ break;
+
+ uint32_t applesauceTicks = (double)ticks * NS_PER_TICK;
+ while (applesauceTicks >= 0xffff)
+ {
+ bw.write_le16(0xffff);
+ applesauceTicks -= 0xffff;
+ }
+ if (applesauceTicks == 0)
+ error("bad data!");
+ bw.write_le16(applesauceTicks);
+ }
+
+ bw.write_le16(0);
+ return asdata;
+}
+
+class ApplesauceException : public ErrorException
+{
+public:
+ ApplesauceException(const std::string& s): ErrorException(s) {}
+};
+
+class ApplesauceUsb : public USB
+{
+public:
+ ApplesauceUsb(const std::string& port, const ApplesauceProto& config):
+ _serial(SerialPort::openSerialPort(port)),
+ _config(config)
+ {
+ std::string s = sendrecv("?");
+ if (s != "Applesauce")
+ error(
+ "Applesauce device not responding (expected 'Applesauce', got "
+ "'{}')",
+ s);
+
+ doCommand("client:v2");
+
+ atexit(
+ []()
+ {
+ delete instance;
+ });
+ }
+
+ ~ApplesauceUsb()
+ {
+ sendrecv("disconnect");
+ }
+
+private:
+ std::string sendrecv(const std::string& command)
+ {
+ if (_config.verbose())
+ fmt::print(fmt::format("> {}\n", command));
+ _serial->writeLine(command);
+ auto r = _serial->readLine();
+ if (_config.verbose())
+ fmt::print(fmt::format("< {}\n", r));
+ return r;
+ }
+
+ void checkCommandResult(const std::string& result)
+ {
+ if (result != ".")
+ throw ApplesauceException(
+ fmt::format("low-level Applesauce error: '{}'", result));
+ }
+
+ void doCommand(const std::string& command)
+ {
+ checkCommandResult(sendrecv(command));
+ }
+
+ std::string doCommandX(const std::string& command)
+ {
+ doCommand(command);
+ std::string r = _serial->readLine();
+ if (_config.verbose())
+ fmt::print(fmt::format("<< {}\n", r));
+ return r;
+ }
+
+ void connect()
+ {
+ if (!_connected)
+ {
+ try
+ {
+ doCommand("connect");
+ doCommand("drive:enable");
+ doCommand("motor:on");
+ doCommand("head:zero");
+ _connected = true;
+ }
+ catch (const ApplesauceException& e)
+ {
+ error("Applesauce could not connect to a drive");
+ }
+ }
+ }
+
+public:
+ void seek(int track) override
+ {
+ if (track == 0)
+ doCommand("head:zero");
+ else
+ doCommand(fmt::format("head:track{}", track));
+ }
+
+ nanoseconds_t getRotationalPeriod(int hardSectorCount) override
+ {
+ if (hardSectorCount != 0)
+ error("hard sectors are currently unsupported on the Applesauce");
+
+ connect();
+ try
+ {
+ double period_us = std::stod(doCommandX("sync:?speed"));
+ _serial->writeByte('X');
+ std::string r = _serial->readLine();
+ if (_config.verbose())
+ fmt::print("<< {}\n", r);
+ return period_us * 1e3;
+ }
+ catch (const ApplesauceException& e)
+ {
+ return 0;
+ }
+ }
+
+ void testBulkWrite() override
+ {
+ int max = std::stoi(sendrecv("data:?max"));
+ fmt::print("Writing data: ");
+
+ doCommand(fmt::format("data:>{}", max));
+
+ Bytes junk(max);
+ uint32_t seed = 0;
+ for (int i = 0; i < max; i++)
+ {
+ junk[i] = seed;
+ seed = ss_rand_next(seed);
+ }
+ double start_time = getCurrentTime();
+ _serial->write(junk);
+ _serial->readLine();
+ double elapsed_time = getCurrentTime() - start_time;
+
+ std::cout << fmt::format(
+ "transferred {} bytes from PC -> device in {} ms ({} kb/s)\n",
+ max,
+ int(elapsed_time * 1000.0),
+ int((max / 1024.0) / elapsed_time));
+ }
+
+ void testBulkRead() override
+ {
+ int max = std::stoi(sendrecv("data:?max"));
+ fmt::print("Reading data: ");
+
+ doCommand(fmt::format("data:<{}", max));
+
+ double start_time = getCurrentTime();
+ _serial->readBytes(max);
+ double elapsed_time = getCurrentTime() - start_time;
+
+ std::cout << fmt::format(
+ "transferred {} bytes from device -> PC in {} ms ({} kb/s)\n",
+ max,
+ int(elapsed_time * 1000.0),
+ int((max / 1024.0) / elapsed_time));
+ }
+
+ Bytes read(int side,
+ bool synced,
+ nanoseconds_t readTime,
+ nanoseconds_t hardSectorThreshold) override
+ {
+ if (hardSectorThreshold != 0)
+ error("hard sectors are currently unsupported on the Applesauce");
+ bool shortRead = readTime < 400e6;
+ warning(
+ "applesauce: timed reads not supported; using read of {} "
+ "revolutions",
+ shortRead ? "1.25" : "2.25");
+
+ connect();
+ doCommand(fmt::format("head:side{}", side));
+ doCommand("sync:on");
+ doCommand("data:clear");
+ std::string r = doCommandX(shortRead ? "disk:read" : "disk:readx");
+ auto rsplit = split(r, '|');
+ if (rsplit.size() < 2)
+ error("unrecognised Applesauce response to disk:read: '{}'", r);
+
+ int bufferSize = std::stoi(rsplit[0]);
+ nanoseconds_t tickSize = std::stod(rsplit[1]) / 1e3;
+
+ std::vector indexMarks;
+ for (auto i = rsplit.begin() + 2; i != rsplit.end(); i++)
+ indexMarks.push_back(std::stoi(*i));
+
+ doCommand(fmt::format("data:<{}", bufferSize));
+
+ Bytes rawData = _serial->readBytes(bufferSize);
+ Bytes b = applesauceReadDataToFluxEngine(rawData, tickSize, indexMarks);
+ return b;
+ }
+
+private:
+ void checkWritable()
+ {
+ if (sendrecv("disk:?write") == "-")
+ error("cannot write --- disk is write protected");
+ if (sendrecv("?safe") == "+")
+ error("cannot write --- Applesauce 'safe' switch is on");
+ if (sendrecv("?vers") < "0300")
+ error("cannot write --- need Applesauce firmware 2.0 or above");
+ }
+
+public:
+ void write(int side,
+ const Bytes& fldata,
+ nanoseconds_t hardSectorThreshold) override
+ {
+ if (hardSectorThreshold != 0)
+ error("hard sectors are currently unsupported on the Applesauce");
+ checkWritable();
+
+ connect();
+ doCommand(fmt::format("head:side{}", side));
+ doCommand("sync:on");
+ doCommand("disk:wipe");
+ doCommand("data:clear");
+ doCommand("disk:wclear");
+
+ Bytes asdata = fluxEngineToApplesauceWriteData(fldata);
+ doCommand(fmt::format("data:>{}", asdata.size()));
+ _serial->write(asdata);
+ checkCommandResult(_serial->readLine());
+ doCommand("disk:wcmd0,0");
+ doCommand("disk:write");
+ }
+
+ void erase(int side, nanoseconds_t hardSectorThreshold) override
+ {
+ if (hardSectorThreshold != 0)
+ error("hard sectors are currently unsupported on the Applesauce");
+ checkWritable();
+
+ connect();
+ doCommand(fmt::format("disk:side{}", side));
+ doCommand("disk:wipe");
+ }
+
+ void setDrive(int drive, bool high_density, int index_mode) override
+ {
+ if (drive != 0)
+ error("the Applesauce only supports drive 0");
+
+ connect();
+ doCommand(fmt::format("dpc:density{}", high_density ? '+' : '-'));
+ }
+
+ void measureVoltages(struct voltages_frame* voltages) override
+ {
+ error("unsupported operation on the Greaseweazle");
+ }
+
+private:
+ std::unique_ptr _serial;
+ const ApplesauceProto& _config;
+ bool _connected = false;
+};
+
+USB* createApplesauceUsb(const std::string& port, const ApplesauceProto& config)
+{
+ instance = new ApplesauceUsb(port, config);
+ return instance;
+}
+
+// vim: sw=4 ts=4 et
diff --git a/lib/usb/fluxengineusb.cc b/lib/usb/fluxengineusb.cc
index d9b9c42a..e7ec6c76 100644
--- a/lib/usb/fluxengineusb.cc
+++ b/lib/usb/fluxengineusb.cc
@@ -122,8 +122,7 @@ class FluxEngineUsb : public USB
}
}
-public:
- int getVersion() override
+ int getVersion()
{
struct any_frame f = {
.f = {.type = F_FRAME_GET_VERSION_CMD, .size = sizeof(f)}
@@ -133,6 +132,7 @@ class FluxEngineUsb : public USB
return r->version;
}
+public:
void seek(int track) override
{
struct seek_frame f = {
diff --git a/lib/usb/greaseweazle.cc b/lib/usb/greaseweazle.cc
index 8b418e4f..b0313555 100644
--- a/lib/usb/greaseweazle.cc
+++ b/lib/usb/greaseweazle.cc
@@ -53,7 +53,7 @@ Bytes fluxEngineToGreaseweazle(const Bytes& fldata, nanoseconds_t clock)
return gwdata;
}
-Bytes greaseWeazleToFluxEngine(const Bytes& gwdata, nanoseconds_t clock)
+Bytes greaseweazleToFluxEngine(const Bytes& gwdata, nanoseconds_t clock)
{
Bytes fldata;
ByteReader br(gwdata);
diff --git a/lib/usb/greaseweazle.h b/lib/usb/greaseweazle.h
index 27381630..a6063d68 100644
--- a/lib/usb/greaseweazle.h
+++ b/lib/usb/greaseweazle.h
@@ -10,7 +10,7 @@
#define EP_IN 0x83
extern Bytes fluxEngineToGreaseweazle(const Bytes& fldata, nanoseconds_t clock);
-extern Bytes greaseWeazleToFluxEngine(const Bytes& gwdata, nanoseconds_t clock);
+extern Bytes greaseweazleToFluxEngine(const Bytes& gwdata, nanoseconds_t clock);
extern Bytes stripPartialRotation(const Bytes& fldata);
/* Copied from
diff --git a/lib/usb/greaseweazleusb.cc b/lib/usb/greaseweazleusb.cc
index 4496adc2..69be470f 100644
--- a/lib/usb/greaseweazleusb.cc
+++ b/lib/usb/greaseweazleusb.cc
@@ -109,7 +109,8 @@ class GreaseweazleUsb : public USB
do_command({CMD_SET_BUS_TYPE, 3, (uint8_t)config.bus_type()});
}
- int getVersion() override
+private:
+ int getVersion()
{
do_command({CMD_GET_INFO, 3, GETINFO_FIRMWARE});
@@ -124,11 +125,7 @@ class GreaseweazleUsb : public USB
return br.read_be16();
}
- void recalibrate() override
- {
- seek(0);
- }
-
+public:
void seek(int track) override
{
do_command({CMD_SEEK, 3, (uint8_t)track});
@@ -356,7 +353,7 @@ class GreaseweazleUsb : public USB
do_command({CMD_GET_FLUX_STATUS, 2});
- Bytes fldata = greaseWeazleToFluxEngine(buffer, _clock);
+ Bytes fldata = greaseweazleToFluxEngine(buffer, _clock);
if (synced)
fldata = stripPartialRotation(fldata);
return fldata;
diff --git a/lib/usb/serial.cc b/lib/usb/serial.cc
index f1d8aea6..4101072b 100644
--- a/lib/usb/serial.cc
+++ b/lib/usb/serial.cc
@@ -254,6 +254,11 @@ uint8_t SerialPort::readByte()
return b;
}
+void SerialPort::writeByte(uint8_t b)
+{
+ this->write(&b, 1);
+}
+
void SerialPort::write(const Bytes& bytes)
{
int ptr = 0;
@@ -264,6 +269,28 @@ void SerialPort::write(const Bytes& bytes)
}
}
+void SerialPort::writeLine(const std::string& chars)
+{
+ this->write((const uint8_t*)&chars[0], chars.size());
+ writeByte('\n');
+}
+
+std::string SerialPort::readLine()
+{
+ std::string s;
+
+ for (;;)
+ {
+ uint8_t b = readByte();
+ if (b == '\r')
+ continue;
+ if (b == '\n')
+ return s;
+
+ s += b;
+ }
+}
+
std::unique_ptr SerialPort::openSerialPort(const std::string& path)
{
return std::make_unique(path);
diff --git a/lib/usb/serial.h b/lib/usb/serial.h
index 7f594102..0fd7416a 100644
--- a/lib/usb/serial.h
+++ b/lib/usb/serial.h
@@ -17,8 +17,12 @@ class SerialPort
void read(Bytes& bytes);
Bytes readBytes(size_t count);
uint8_t readByte();
+ void writeByte(uint8_t b);
void write(const Bytes& bytes);
+ void writeLine(const std::string& chars);
+ std::string readLine();
+
private:
uint8_t _readbuffer[4096];
size_t _readbuffer_ptr = 0;
diff --git a/lib/usb/usb.cc b/lib/usb/usb.cc
index a6654796..201c3192 100644
--- a/lib/usb/usb.cc
+++ b/lib/usb/usb.cc
@@ -9,6 +9,7 @@
#include "lib/proto.h"
#include "usbfinder.h"
#include "lib/logger.h"
+#include "applesauce.h"
#include "greaseweazle.h"
static USB* usb = NULL;
@@ -54,6 +55,11 @@ static std::shared_ptr selectDevice()
std::cerr << fmt::format(
"Greaseweazle: {} on {}\n", c->serial, c->serialPort);
break;
+
+ case APPLESAUCE_ID:
+ std::cerr << fmt::format(
+ "Applesauce: {} on {}\n", c->serial, c->serialPort);
+ break;
}
}
exit(1);
@@ -71,6 +77,14 @@ USB* get_usb_impl()
return createGreaseweazleUsb(conf.port(), conf);
}
+ if (globalConfig()->usb().has_applesauce() &&
+ globalConfig()->usb().applesauce().has_port())
+ {
+ const auto& conf = globalConfig()->usb().applesauce();
+ log("Using Applesauce on serial port {}", conf.port());
+ return createApplesauceUsb(conf.port(), conf);
+ }
+
/* Otherwise, select a device by USB ID. */
auto candidate = selectDevice();
@@ -87,6 +101,13 @@ USB* get_usb_impl()
return createGreaseweazleUsb(
candidate->serialPort, globalConfig()->usb().greaseweazle());
+ case APPLESAUCE_ID:
+ log("Using Applesauce {} on {}",
+ candidate->serial,
+ candidate->serialPort);
+ return createApplesauceUsb(
+ candidate->serialPort, globalConfig()->usb().applesauce());
+
default:
error("internal");
}
diff --git a/lib/usb/usb.h b/lib/usb/usb.h
index 7686ad07..d9a612c8 100644
--- a/lib/usb/usb.h
+++ b/lib/usb/usb.h
@@ -6,6 +6,7 @@
class Fluxmap;
class GreaseweazleProto;
+class ApplesauceProto;
namespace libusbp
{
class device;
@@ -16,8 +17,10 @@ class USB
public:
virtual ~USB();
- virtual int getVersion() = 0;
- virtual void recalibrate() = 0;
+ virtual void recalibrate()
+ {
+ seek(0);
+ };
virtual void seek(int track) = 0;
virtual nanoseconds_t getRotationalPeriod(int hardSectorCount) = 0;
virtual void testBulkWrite() = 0;
@@ -41,11 +44,9 @@ extern USB& getUsb();
extern USB* createFluxengineUsb(libusbp::device& device);
extern USB* createGreaseweazleUsb(
const std::string& serialPort, const GreaseweazleProto& config);
+extern USB* createApplesauceUsb(
+ const std::string& serialPort, const ApplesauceProto& config);
-static inline int usbGetVersion()
-{
- return getUsb().getVersion();
-}
static inline void usbRecalibrate()
{
getUsb().recalibrate();
diff --git a/lib/usb/usb.proto b/lib/usb/usb.proto
index 2c61a91a..14f865fc 100644
--- a/lib/usb/usb.proto
+++ b/lib/usb/usb.proto
@@ -16,9 +16,17 @@ message GreaseweazleProto {
[(help) = "which FDD bus type is in use", default = IBMPC];
}
+message ApplesauceProto {
+ optional string port = 1
+ [(help) = "Applesauce serial port to use"];
+ optional bool verbose = 2
+ [(help) = "Enable verbose protocol logging", default = false];
+}
+
message UsbProto {
optional string serial = 1
[(help) = "serial number of FluxEngine or Greaseweazle device to use"];
optional GreaseweazleProto greaseweazle = 2 [(help) = "Greaseweazle-specific options"];
+ optional ApplesauceProto applesauce = 3 [(help) = "Applesauce-specific options"];
}
diff --git a/lib/usb/usbfinder.cc b/lib/usb/usbfinder.cc
index d3a87003..1d98174f 100644
--- a/lib/usb/usbfinder.cc
+++ b/lib/usb/usbfinder.cc
@@ -3,12 +3,13 @@
#include "usb.h"
#include "lib/bytes.h"
#include "usbfinder.h"
+#include "applesauce.h"
#include "greaseweazle.h"
#include "protocol.h"
#include "libusbp.hpp"
static const std::set VALID_DEVICES = {
- GREASEWEAZLE_ID, FLUXENGINE_ID};
+ GREASEWEAZLE_ID, FLUXENGINE_ID, APPLESAUCE_ID};
static const std::string get_serial_number(const libusbp::device& device)
{
@@ -41,13 +42,17 @@ std::vector> findUsbDevices()
candidate->serial = get_serial_number(it);
if (id == GREASEWEAZLE_ID)
+ candidate->type = DEVICE_GREASEWEAZLE;
+ else if (id == APPLESAUCE_ID)
+ candidate->type = DEVICE_APPLESAUCE;
+ else if (id == FLUXENGINE_ID)
+ candidate->type = DEVICE_FLUXENGINE;
+
+ if ((id == GREASEWEAZLE_ID) || (id == APPLESAUCE_ID))
{
libusbp::serial_port port(candidate->device);
candidate->serialPort = port.get_name();
- candidate->type = DEVICE_GREASEWEAZLE;
}
- else if (id == FLUXENGINE_ID)
- candidate->type = DEVICE_FLUXENGINE;
candidates.push_back(std::move(candidate));
}
@@ -71,6 +76,9 @@ std::string getDeviceName(DeviceType type)
case DEVICE_FLUXENGINE:
return "FluxEngine";
+ case DEVICE_APPLESAUCE:
+ return "Applesauce";
+
default:
return "unknown";
}
diff --git a/lib/usb/usbfinder.h b/lib/usb/usbfinder.h
index 2034a7d8..435c7e3b 100644
--- a/lib/usb/usbfinder.h
+++ b/lib/usb/usbfinder.h
@@ -7,7 +7,8 @@
enum DeviceType
{
DEVICE_FLUXENGINE,
- DEVICE_GREASEWEAZLE
+ DEVICE_GREASEWEAZLE,
+ DEVICE_APPLESAUCE,
};
extern std::string getDeviceName(DeviceType type);
diff --git a/tests/applesauce.cc b/tests/applesauce.cc
new file mode 100644
index 00000000..93841174
--- /dev/null
+++ b/tests/applesauce.cc
@@ -0,0 +1,74 @@
+#include
+#include
+#include
+#include "lib/globals.h"
+#include "lib/fluxmap.h"
+#include "lib/usb/applesauce.h"
+
+static void test_convert(const Bytes& asbytes, const Bytes& flbytes)
+{
+ Bytes astoflbytes = applesauceToFluxEngine(asbytes);
+ Bytes fltoasbytes = fluxEngineToApplesauce(flbytes);
+
+ if (astoflbytes != flbytes)
+ {
+ std::cout << "Applesauce to FluxEngine conversion failed.\n";
+ std::cout << "Applesauce bytes:" << std::endl;
+ hexdump(std::cout, asbytes);
+ std::cout << std::endl << "Produced this:" << std::endl;
+ hexdump(std::cout, astoflbytes);
+ std::cout << std::endl << "Expected this:" << std::endl;
+ hexdump(std::cout, flbytes);
+ abort();
+ }
+
+ if (fltoasbytes != asbytes)
+ {
+ std::cout << "FluxEngine to Applesauce conversion failed.\n";
+ std::cout << "FluxEngine bytes:" << std::endl;
+ hexdump(std::cout, flbytes);
+ std::cout << std::endl << "Produced this:" << std::endl;
+ hexdump(std::cout, fltoasbytes);
+ std::cout << std::endl << "Expected this:" << std::endl;
+ hexdump(std::cout, asbytes);
+ abort();
+ }
+}
+
+static void test_conversions()
+{
+ /* Simple one-byte intervals. */
+
+ test_convert(Bytes{0x20, 0x20, 0x20, 0x20}, Bytes{0xb0, 0xb0, 0xb0, 0xb0});
+
+ /* Long, multibyte intervals. */
+
+ test_convert(Bytes{0xff, 0x1f, 0x20, 0xff, 0xff, 0x20},
+ Bytes{0x3f,
+ 0x3f,
+ 0x3f,
+ 0x3f,
+ 0x3f,
+ 0x3f,
+ 0xb3,
+ 0xb0,
+ 0x3f,
+ 0x3f,
+ 0x3f,
+ 0x3f,
+ 0x3f,
+ 0x3f,
+ 0x3f,
+ 0x3f,
+ 0x3f,
+ 0x3f,
+ 0x3f,
+ 0x3f,
+ 0xb9});
+}
+
+int main(int argc, const char* argv[])
+{
+ test_conversions();
+ return 0;
+}
diff --git a/tests/build.py b/tests/build.py
index 0a951375..08eb68e1 100644
--- a/tests/build.py
+++ b/tests/build.py
@@ -18,6 +18,7 @@
"agg",
"amiga",
"applesingle",
+ "applesauce",
"bitaccumulator",
"bytes",
"compression",
diff --git a/tests/greaseweazle.cc b/tests/greaseweazle.cc
index 8ddbc8dd..e0b9c23b 100644
--- a/tests/greaseweazle.cc
+++ b/tests/greaseweazle.cc
@@ -11,7 +11,7 @@
static void test_convert(const Bytes& gwbytes, const Bytes& flbytes)
{
- Bytes gwtoflbytes = greaseWeazleToFluxEngine(gwbytes, 2 * NS_PER_TICK);
+ Bytes gwtoflbytes = greaseweazleToFluxEngine(gwbytes, 2 * NS_PER_TICK);
Bytes fltogwbytes = fluxEngineToGreaseweazle(flbytes, 2 * NS_PER_TICK);
if (gwtoflbytes != flbytes)