This commit is contained in:
David Given
2024-10-01 23:58:48 +02:00
26 changed files with 678 additions and 45 deletions

View File

@@ -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.
<div style="text-align: center">
<a href="doc/screenshot.jpg"><img src="doc/screenshot.jpg" style="width:60%" alt="screenshot of the GUI in action"></a>
@@ -35,12 +32,14 @@ Don't believe me? Watch the demo reel!
</div>
**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?
------

View File

@@ -85,6 +85,8 @@ cxxlibrary(
"./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",
@@ -180,6 +182,7 @@ cxxlibrary(
"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",

72
doc/applesauce.md Normal file
View File

@@ -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.

View File

@@ -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

View File

@@ -48,7 +48,7 @@ bool FluxmapReader::findEvent(int event, unsigned& ticks)
{
ticks = 0;
for (;;)
while (!eof())
{
unsigned thisTicks;
int thisEvent;
@@ -58,11 +58,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)

View File

@@ -157,7 +157,7 @@ std::vector<std::string> FlagGroup::parseFlagsWithFilenames(int argc,
globalConfig().applyOption(path);
}
else
error("unrecognised flag; try --help");
error("unrecognised flag '-{}'; try --help", key);
}
else
{

View File

@@ -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 {}

View File

@@ -18,9 +18,9 @@ public:
{
auto fluxmap = std::make_unique<Fluxmap>();
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();
}

View File

@@ -14,5 +14,4 @@ double getCurrentTime(void)
void warning(const std::string msg)
{
log(msg);
fmt::print("Warning: {}\n", msg);
}

64
lib/usb/applesauce.cc Normal file
View File

@@ -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();
}

9
lib/usb/applesauce.h Normal file
View File

@@ -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);

345
lib/usb/applesauceusb.cc Normal file
View File

@@ -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 <unistd.h>
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<unsigned>& 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<unsigned> 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<SerialPort> _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

View File

@@ -122,8 +122,7 @@ private:
}
}
public:
int getVersion() override
int getVersion()
{
struct any_frame f = {
.f = {.type = F_FRAME_GET_VERSION_CMD, .size = sizeof(f)}
@@ -133,6 +132,7 @@ public:
return r->version;
}
public:
void seek(int track) override
{
struct seek_frame f = {

View File

@@ -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);

View File

@@ -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

View File

@@ -109,7 +109,8 @@ public:
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 @@ public:
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 @@ public:
do_command({CMD_GET_FLUX_STATUS, 2});
Bytes fldata = greaseWeazleToFluxEngine(buffer, _clock);
Bytes fldata = greaseweazleToFluxEngine(buffer, _clock);
if (synced)
fldata = stripPartialRotation(fldata);
return fldata;

View File

@@ -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> SerialPort::openSerialPort(const std::string& path)
{
return std::make_unique<SerialPortImpl>(path);

View File

@@ -17,8 +17,12 @@ public:
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;

View File

@@ -10,6 +10,7 @@
#include "lib/proto.h"
#include "usbfinder.h"
#include "lib/logger.h"
#include "applesauce.h"
#include "greaseweazle.h"
static USB* usb = NULL;
@@ -55,6 +56,11 @@ static std::shared_ptr<CandidateDevice> 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);
@@ -72,6 +78,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();
@@ -88,6 +102,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");
}

View File

@@ -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();

View File

@@ -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"];
}

View File

@@ -3,12 +3,13 @@
#include "usb.h"
#include "lib/core/bytes.h"
#include "usbfinder.h"
#include "applesauce.h"
#include "greaseweazle.h"
#include "protocol.h"
#include "libusbp.hpp"
static const std::set<uint32_t> 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<std::shared_ptr<CandidateDevice>> 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";
}

View File

@@ -7,7 +7,8 @@
enum DeviceType
{
DEVICE_FLUXENGINE,
DEVICE_GREASEWEAZLE
DEVICE_GREASEWEAZLE,
DEVICE_APPLESAUCE,
};
extern std::string getDeviceName(DeviceType type);

74
tests/applesauce.cc Normal file
View File

@@ -0,0 +1,74 @@
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include "lib/core/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;
}

View File

@@ -18,6 +18,7 @@ tests = [
"agg",
"amiga",
"applesingle",
"applesauce",
"bitaccumulator",
"bytes",
"compression",

View File

@@ -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)