466 lines
11 KiB
C++
466 lines
11 KiB
C++
/*
|
|
* Copyright 2012-2017, Adrien Destugues, pulkomandy@gmail.com
|
|
* Distributed under the terms of the MIT licence.
|
|
*/
|
|
|
|
|
|
#include "SerialApp.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <Directory.h>
|
|
#include <Entry.h>
|
|
#include <File.h>
|
|
#include <FindDirectory.h>
|
|
#include <Path.h>
|
|
|
|
#include "CustomRateWindow.h"
|
|
#include "SerialWindow.h"
|
|
|
|
|
|
static property_info sProperties[] = {
|
|
{ "baudrate",
|
|
{ B_GET_PROPERTY, B_SET_PROPERTY, 0 },
|
|
{ B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
|
|
"get or set the baudrate",
|
|
0, { B_INT32_TYPE }
|
|
},
|
|
{ "bits",
|
|
{ B_GET_PROPERTY, B_SET_PROPERTY, 0 },
|
|
{ B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
|
|
"get or set the number of data bits (7 or 8)",
|
|
0, { B_INT32_TYPE }
|
|
},
|
|
{ "stopbits",
|
|
{ B_GET_PROPERTY, B_SET_PROPERTY, 0 },
|
|
{ B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
|
|
"get or set the number of stop bits (1 or 2)",
|
|
0, { B_INT32_TYPE }
|
|
},
|
|
{ "parity",
|
|
{ B_GET_PROPERTY, B_SET_PROPERTY, 0 },
|
|
{ B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
|
|
"get or set the parity (none, even or odd)",
|
|
0, { B_STRING_TYPE }
|
|
},
|
|
{ "flowcontrol",
|
|
{ B_GET_PROPERTY, B_SET_PROPERTY, 0 },
|
|
{ B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
|
|
"get or set the flow control (hardware, software, both, or none)",
|
|
0, { B_STRING_TYPE }
|
|
},
|
|
{ "port",
|
|
{ B_GET_PROPERTY, B_SET_PROPERTY, B_DELETE_PROPERTY, 0 },
|
|
{ B_DIRECT_SPECIFIER, 0 },
|
|
"get or set the port device",
|
|
0, { B_STRING_TYPE }
|
|
},
|
|
{ 0 }
|
|
};
|
|
|
|
const BPropertyInfo SerialApp::kScriptingProperties(sProperties);
|
|
|
|
|
|
SerialApp::SerialApp()
|
|
: BApplication(SerialApp::kApplicationSignature)
|
|
, fLogFile(NULL)
|
|
, fFileSender(NULL)
|
|
{
|
|
fWindow = new SerialWindow();
|
|
|
|
fSerialLock = create_sem(0, "Serial port lock");
|
|
thread_id id = spawn_thread(PollSerial, "Serial port poller",
|
|
B_LOW_PRIORITY, this);
|
|
resume_thread(id);
|
|
}
|
|
|
|
|
|
SerialApp::~SerialApp()
|
|
{
|
|
delete fLogFile;
|
|
delete fFileSender;
|
|
}
|
|
|
|
|
|
void SerialApp::ReadyToRun()
|
|
{
|
|
LoadSettings();
|
|
fWindow->Show();
|
|
}
|
|
|
|
|
|
void SerialApp::MessageReceived(BMessage* message)
|
|
{
|
|
switch (message->what) {
|
|
case kMsgOpenPort:
|
|
{
|
|
if (message->FindString("port name", &fPortPath) == B_OK) {
|
|
fSerialPort.Open(fPortPath);
|
|
release_sem(fSerialLock);
|
|
} else {
|
|
fSerialPort.Close();
|
|
}
|
|
|
|
// Forward to the window so it can enable/disable menu items
|
|
fWindow->PostMessage(message);
|
|
return;
|
|
}
|
|
case kMsgDataRead:
|
|
{
|
|
const uint8_t* bytes;
|
|
ssize_t length;
|
|
message->FindData("data", B_RAW_TYPE, (const void**)&bytes,
|
|
&length);
|
|
|
|
if (fFileSender != NULL) {
|
|
if (fFileSender->BytesReceived(bytes, length)) {
|
|
delete fFileSender;
|
|
fFileSender = NULL;
|
|
}
|
|
} else {
|
|
// forward the message to the window, which will display the
|
|
// incoming data
|
|
fWindow->PostMessage(message);
|
|
|
|
if (fLogFile) {
|
|
if (fLogFile->Write(bytes, length) != length) {
|
|
// TODO error handling
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
case kMsgDataWrite:
|
|
{
|
|
// Do not allow sending if a file transfer is in progress.
|
|
if (fFileSender != NULL)
|
|
return;
|
|
|
|
const char* bytes;
|
|
ssize_t size;
|
|
|
|
if (message->FindData("data", B_RAW_TYPE, (const void**)&bytes,
|
|
&size) == B_OK)
|
|
fSerialPort.Write(bytes, size);
|
|
return;
|
|
}
|
|
case kMsgLogfile:
|
|
{
|
|
entry_ref parent;
|
|
const char* filename;
|
|
|
|
if (message->FindRef("directory", &parent) == B_OK
|
|
&& message->FindString("name", &filename) == B_OK) {
|
|
delete fLogFile;
|
|
BDirectory directory(&parent);
|
|
fLogFile = new BFile(&directory, filename,
|
|
B_WRITE_ONLY | B_CREATE_FILE | B_OPEN_AT_END);
|
|
status_t error = fLogFile->InitCheck();
|
|
if (error != B_OK)
|
|
puts(strerror(error));
|
|
} else
|
|
debugger("Invalid BMessage received");
|
|
return;
|
|
}
|
|
case kMsgSendFile:
|
|
{
|
|
entry_ref ref;
|
|
|
|
BString protocol = message->FindString("protocol");
|
|
|
|
if (message->FindRef("refs", &ref) == B_OK) {
|
|
BFile* file = new BFile(&ref, B_READ_ONLY);
|
|
status_t error = file->InitCheck();
|
|
if (error != B_OK)
|
|
puts(strerror(error));
|
|
else {
|
|
delete fFileSender;
|
|
if (protocol == "xmodem")
|
|
fFileSender = new XModemSender(file, &fSerialPort, fWindow);
|
|
else
|
|
fFileSender = new RawSender(file, &fSerialPort, fWindow);
|
|
}
|
|
} else {
|
|
message->PrintToStream();
|
|
debugger("Invalid BMessage received");
|
|
}
|
|
return;
|
|
}
|
|
case kMsgCustomBaudrate:
|
|
{
|
|
// open the custom baudrate selector window
|
|
CustomRateWindow* window = new CustomRateWindow(fSerialPort.DataRate());
|
|
window->Show();
|
|
return;
|
|
}
|
|
case kMsgSettings:
|
|
{
|
|
int32 baudrate;
|
|
stop_bits stopBits;
|
|
data_bits dataBits;
|
|
parity_mode parity;
|
|
uint32 flowcontrol;
|
|
|
|
if (message->FindInt32("databits", (int32*)&dataBits) == B_OK)
|
|
fSerialPort.SetDataBits(dataBits);
|
|
|
|
if (message->FindInt32("stopbits", (int32*)&stopBits) == B_OK)
|
|
fSerialPort.SetStopBits(stopBits);
|
|
|
|
if (message->FindInt32("parity", (int32*)&parity) == B_OK)
|
|
fSerialPort.SetParityMode(parity);
|
|
|
|
if (message->FindInt32("flowcontrol", (int32*)&flowcontrol) == B_OK)
|
|
fSerialPort.SetFlowControl(flowcontrol);
|
|
|
|
if (message->FindInt32("baudrate", &baudrate) == B_OK) {
|
|
data_rate rate = (data_rate)baudrate;
|
|
fSerialPort.SetDataRate(rate);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Handle scripting messages
|
|
if (message->HasSpecifiers()) {
|
|
BMessage specifier;
|
|
int32 what;
|
|
int32 index;
|
|
const char* property;
|
|
|
|
BMessage reply(B_REPLY);
|
|
BMessage settings(kMsgSettings);
|
|
bool settingsChanged = false;
|
|
|
|
if (message->GetCurrentSpecifier(&index, &specifier, &what, &property)
|
|
== B_OK) {
|
|
switch (kScriptingProperties.FindMatch(message, index, &specifier,
|
|
what, property)) {
|
|
case 0: // baudrate
|
|
if (message->what == B_GET_PROPERTY) {
|
|
reply.AddInt32("result", fSerialPort.DataRate());
|
|
message->SendReply(&reply);
|
|
return;
|
|
}
|
|
if (message->what == B_SET_PROPERTY) {
|
|
int32 rate = message->FindInt32("data");
|
|
settingsChanged = true;
|
|
settings.AddInt32("baudrate", rate);
|
|
}
|
|
break;
|
|
case 1: // data bits
|
|
if (message->what == B_GET_PROPERTY) {
|
|
reply.AddInt32("result", fSerialPort.DataBits() + 7);
|
|
message->SendReply(&reply);
|
|
return;
|
|
}
|
|
if (message->what == B_SET_PROPERTY) {
|
|
int32 bits = message->FindInt32("data");
|
|
settingsChanged = true;
|
|
settings.AddInt32("databits", bits - 7);
|
|
}
|
|
break;
|
|
case 2: // stop bits
|
|
if (message->what == B_GET_PROPERTY) {
|
|
reply.AddInt32("result", fSerialPort.StopBits() + 1);
|
|
message->SendReply(&reply);
|
|
return;
|
|
}
|
|
if (message->what == B_SET_PROPERTY) {
|
|
int32 bits = message->FindInt32("data");
|
|
settingsChanged = true;
|
|
settings.AddInt32("stopbits", bits - 1);
|
|
}
|
|
break;
|
|
case 3: // parity
|
|
{
|
|
static const char* strings[] = {"none", "odd", "even"};
|
|
if (message->what == B_GET_PROPERTY) {
|
|
reply.AddString("result",
|
|
strings[fSerialPort.ParityMode()]);
|
|
message->SendReply(&reply);
|
|
return;
|
|
}
|
|
if (message->what == B_SET_PROPERTY) {
|
|
BString bits = message->FindString("data");
|
|
int i;
|
|
for (i = 0; i < 3; i++) {
|
|
if (bits == strings[i])
|
|
break;
|
|
}
|
|
|
|
if (i < 3) {
|
|
settingsChanged = true;
|
|
settings.AddInt32("parity", i);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 4: // flow control
|
|
{
|
|
static const char* strings[] = {"none", "hardware",
|
|
"software", "both"};
|
|
if (message->what == B_GET_PROPERTY) {
|
|
reply.AddString("result",
|
|
strings[fSerialPort.FlowControl()]);
|
|
message->SendReply(&reply);
|
|
return;
|
|
}
|
|
if (message->what == B_SET_PROPERTY) {
|
|
BString bits = message->FindString("data");
|
|
int i;
|
|
for (i = 0; i < 4; i++) {
|
|
if (bits == strings[i])
|
|
break;
|
|
}
|
|
|
|
if (i < 4) {
|
|
settingsChanged = true;
|
|
settings.AddInt32("flowcontrol", i);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 5: // port
|
|
if (message->what == B_GET_PROPERTY) {
|
|
reply.AddString("port", GetPort());
|
|
message->SendReply(&reply);
|
|
} else if (message->what == B_DELETE_PROPERTY
|
|
|| message->what == B_SET_PROPERTY) {
|
|
BString path = message->FindString("data");
|
|
BMessage openMessage(kMsgOpenPort);
|
|
openMessage.AddString("port name", path);
|
|
PostMessage(&openMessage);
|
|
fWindow->PostMessage(&openMessage);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (settingsChanged) {
|
|
PostMessage(&settings);
|
|
fWindow->PostMessage(&settings);
|
|
return;
|
|
}
|
|
}
|
|
|
|
BApplication::MessageReceived(message);
|
|
}
|
|
|
|
|
|
bool SerialApp::QuitRequested()
|
|
{
|
|
if (BApplication::QuitRequested()) {
|
|
SaveSettings();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
const BString& SerialApp::GetPort()
|
|
{
|
|
return fPortPath;
|
|
}
|
|
|
|
|
|
void SerialApp::LoadSettings()
|
|
{
|
|
BPath path;
|
|
find_directory(B_USER_SETTINGS_DIRECTORY, &path);
|
|
path.Append("SerialConnect");
|
|
|
|
BFile file(path.Path(), B_READ_ONLY);
|
|
BMessage message(kMsgSettings);
|
|
if (message.Unflatten(&file) != B_OK) {
|
|
message.AddInt32("parity", fSerialPort.ParityMode());
|
|
message.AddInt32("databits", fSerialPort.DataBits());
|
|
message.AddInt32("stopbits", fSerialPort.StopBits());
|
|
message.AddInt32("baudrate", fSerialPort.DataRate());
|
|
message.AddInt32("flowcontrol", fSerialPort.FlowControl());
|
|
}
|
|
|
|
be_app->PostMessage(&message);
|
|
fWindow->PostMessage(&message);
|
|
}
|
|
|
|
|
|
void SerialApp::SaveSettings()
|
|
{
|
|
BMessage message(kMsgSettings);
|
|
message.AddInt32("parity", fSerialPort.ParityMode());
|
|
message.AddInt32("databits", fSerialPort.DataBits());
|
|
message.AddInt32("stopbits", fSerialPort.StopBits());
|
|
message.AddInt32("baudrate", fSerialPort.DataRate());
|
|
message.AddInt32("flowcontrol", fSerialPort.FlowControl());
|
|
|
|
BPath path;
|
|
find_directory(B_USER_SETTINGS_DIRECTORY, &path);
|
|
path.Append("SerialConnect");
|
|
|
|
BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE);
|
|
message.Flatten(&file);
|
|
}
|
|
|
|
|
|
/* static */
|
|
status_t SerialApp::PollSerial(void*)
|
|
{
|
|
SerialApp* application = (SerialApp*)be_app;
|
|
char buffer[256];
|
|
|
|
for (;;) {
|
|
ssize_t bytesRead;
|
|
|
|
bytesRead = application->fSerialPort.Read(buffer, sizeof(buffer));
|
|
if (bytesRead == B_FILE_ERROR) {
|
|
// Port is not open - wait for it and start over
|
|
acquire_sem(application->fSerialLock);
|
|
} else if (bytesRead > 0) {
|
|
// We read something, forward it to the app for handling
|
|
BMessage* serialData = new BMessage(kMsgDataRead);
|
|
serialData->AddData("data", B_RAW_TYPE, buffer, bytesRead);
|
|
be_app_messenger.SendMessage(serialData);
|
|
}
|
|
}
|
|
|
|
// Should not reach this line anyway...
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
const char* SerialApp::kApplicationSignature
|
|
= "application/x-vnd.haiku.SerialConnect";
|
|
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
SerialApp app;
|
|
app.Run();
|
|
}
|
|
|
|
|
|
status_t
|
|
SerialApp::GetSupportedSuites(BMessage* message)
|
|
{
|
|
message->AddString("suites", "suite/vnd.Haiku-SerialPort");
|
|
message->AddFlat("messages", &kScriptingProperties);
|
|
return BApplication::GetSupportedSuites(message);
|
|
}
|
|
|
|
|
|
BHandler*
|
|
SerialApp::ResolveSpecifier(BMessage* message, int32 index,
|
|
BMessage* specifier, int32 what, const char* property)
|
|
{
|
|
if (kScriptingProperties.FindMatch(message, index, specifier, what,
|
|
property) >= 0)
|
|
return this;
|
|
|
|
return BApplication::ResolveSpecifier(message, index, specifier, what,
|
|
property);
|
|
}
|