1/*
2 * Copyright 2012-2017, Adrien Destugues, pulkomandy@gmail.com
3 * Distributed under the terms of the MIT licence.
4 */
5
6
7#include "SerialApp.h"
8
9#include <stdio.h>
10#include <string.h>
11
12#include <Directory.h>
13#include <Entry.h>
14#include <File.h>
15#include <FindDirectory.h>
16#include <Path.h>
17
18#include "CustomRateWindow.h"
19#include "SerialWindow.h"
20
21
22static property_info sProperties[] = {
23	{ "baudrate",
24		{ B_GET_PROPERTY, B_SET_PROPERTY, 0 },
25		{ B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
26		"get or set the baudrate",
27		0, { B_INT32_TYPE }
28	},
29	{ "bits",
30		{ B_GET_PROPERTY, B_SET_PROPERTY, 0 },
31		{ B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
32		"get or set the number of data bits (7 or 8)",
33		0, { B_INT32_TYPE }
34	},
35	{ "stopbits",
36		{ B_GET_PROPERTY, B_SET_PROPERTY, 0 },
37		{ B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
38		"get or set the number of stop bits (1 or 2)",
39		0, { B_INT32_TYPE }
40	},
41	{ "parity",
42		{ B_GET_PROPERTY, B_SET_PROPERTY, 0 },
43		{ B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
44		"get or set the parity (none, even or odd)",
45		0, { B_STRING_TYPE }
46	},
47	{ "flowcontrol",
48		{ B_GET_PROPERTY, B_SET_PROPERTY, 0 },
49		{ B_DIRECT_SPECIFIER, B_DIRECT_SPECIFIER, 0 },
50		"get or set the flow control (hardware, software, both, or none)",
51		0, { B_STRING_TYPE }
52	},
53	{ "port",
54		{ B_GET_PROPERTY, B_SET_PROPERTY, B_DELETE_PROPERTY, 0 },
55		{ B_DIRECT_SPECIFIER, 0 },
56		"get or set the port device",
57		0, { B_STRING_TYPE }
58	},
59	{ 0 }
60};
61
62const BPropertyInfo SerialApp::kScriptingProperties(sProperties);
63
64
65SerialApp::SerialApp()
66	: BApplication(SerialApp::kApplicationSignature)
67	, fLogFile(NULL)
68	, fFileSender(NULL)
69{
70	fWindow = new SerialWindow();
71
72	fSerialLock = create_sem(0, "Serial port lock");
73	thread_id id = spawn_thread(PollSerial, "Serial port poller",
74		B_LOW_PRIORITY, this);
75	resume_thread(id);
76}
77
78
79SerialApp::~SerialApp()
80{
81	delete fLogFile;
82	delete fFileSender;
83}
84
85
86void SerialApp::ReadyToRun()
87{
88	LoadSettings();
89	fWindow->Show();
90}
91
92
93void SerialApp::MessageReceived(BMessage* message)
94{
95	switch (message->what) {
96		case kMsgOpenPort:
97		{
98			if (message->FindString("port name", &fPortPath) == B_OK) {
99				fSerialPort.Open(fPortPath);
100				release_sem(fSerialLock);
101			} else {
102				fSerialPort.Close();
103			}
104
105			// Forward to the window so it can enable/disable menu items
106			fWindow->PostMessage(message);
107			return;
108		}
109		case kMsgDataRead:
110		{
111			const uint8_t* bytes;
112			ssize_t length;
113			message->FindData("data", B_RAW_TYPE, (const void**)&bytes,
114				&length);
115
116			if (fFileSender != NULL) {
117				if (fFileSender->BytesReceived(bytes, length)) {
118					delete fFileSender;
119					fFileSender = NULL;
120				}
121			} else {
122				// forward the message to the window, which will display the
123				// incoming data
124				fWindow->PostMessage(message);
125
126				if (fLogFile) {
127					if (fLogFile->Write(bytes, length) != length) {
128						// TODO error handling
129					}
130				}
131			}
132
133			return;
134		}
135		case kMsgDataWrite:
136		{
137			// Do not allow sending if a file transfer is in progress.
138			if (fFileSender != NULL)
139				return;
140
141			const char* bytes;
142			ssize_t size;
143
144			if (message->FindData("data", B_RAW_TYPE, (const void**)&bytes,
145					&size) == B_OK)
146				fSerialPort.Write(bytes, size);
147			return;
148		}
149		case kMsgLogfile:
150		{
151			entry_ref parent;
152			const char* filename;
153
154			if (message->FindRef("directory", &parent) == B_OK
155				&& message->FindString("name", &filename) == B_OK) {
156				delete fLogFile;
157				BDirectory directory(&parent);
158				fLogFile = new BFile(&directory, filename,
159					B_WRITE_ONLY | B_CREATE_FILE | B_OPEN_AT_END);
160				status_t error = fLogFile->InitCheck();
161				if (error != B_OK)
162					puts(strerror(error));
163			} else
164				debugger("Invalid BMessage received");
165			return;
166		}
167		case kMsgSendFile:
168		{
169			entry_ref ref;
170
171			BString protocol = message->FindString("protocol");
172
173			if (message->FindRef("refs", &ref) == B_OK) {
174				BFile* file = new BFile(&ref, B_READ_ONLY);
175				status_t error = file->InitCheck();
176				if (error != B_OK)
177					puts(strerror(error));
178				else {
179					delete fFileSender;
180					if (protocol == "xmodem")
181						fFileSender = new XModemSender(file, &fSerialPort, fWindow);
182					else
183						fFileSender = new RawSender(file, &fSerialPort, fWindow);
184				}
185			} else {
186				message->PrintToStream();
187				debugger("Invalid BMessage received");
188			}
189			return;
190		}
191		case kMsgCustomBaudrate:
192		{
193			// open the custom baudrate selector window
194			CustomRateWindow* window = new CustomRateWindow(fSerialPort.DataRate());
195			window->Show();
196			return;
197		}
198		case kMsgSettings:
199		{
200			int32 baudrate;
201			stop_bits stopBits;
202			data_bits dataBits;
203			parity_mode parity;
204			uint32 flowcontrol;
205
206			if (message->FindInt32("databits", (int32*)&dataBits) == B_OK)
207				fSerialPort.SetDataBits(dataBits);
208
209			if (message->FindInt32("stopbits", (int32*)&stopBits) == B_OK)
210				fSerialPort.SetStopBits(stopBits);
211
212			if (message->FindInt32("parity", (int32*)&parity) == B_OK)
213				fSerialPort.SetParityMode(parity);
214
215			if (message->FindInt32("flowcontrol", (int32*)&flowcontrol) == B_OK)
216				fSerialPort.SetFlowControl(flowcontrol);
217
218			if (message->FindInt32("baudrate", &baudrate) == B_OK) {
219				data_rate rate = (data_rate)baudrate;
220				fSerialPort.SetDataRate(rate);
221			}
222
223			return;
224		}
225	}
226
227	// Handle scripting messages
228	if (message->HasSpecifiers()) {
229		BMessage specifier;
230		int32 what;
231		int32 index;
232		const char* property;
233
234		BMessage reply(B_REPLY);
235		BMessage settings(kMsgSettings);
236		bool settingsChanged = false;
237
238		if (message->GetCurrentSpecifier(&index, &specifier, &what, &property)
239			== B_OK) {
240			switch (kScriptingProperties.FindMatch(message, index, &specifier,
241				what, property)) {
242				case 0: // baudrate
243					if (message->what == B_GET_PROPERTY) {
244						reply.AddInt32("result", fSerialPort.DataRate());
245						message->SendReply(&reply);
246						return;
247					}
248					if (message->what == B_SET_PROPERTY) {
249						int32 rate = message->FindInt32("data");
250						settingsChanged = true;
251						settings.AddInt32("baudrate", rate);
252					}
253					break;
254				case 1: // data bits
255					if (message->what == B_GET_PROPERTY) {
256						reply.AddInt32("result", fSerialPort.DataBits() + 7);
257						message->SendReply(&reply);
258						return;
259					}
260					if (message->what == B_SET_PROPERTY) {
261						int32 bits = message->FindInt32("data");
262						settingsChanged = true;
263						settings.AddInt32("databits", bits - 7);
264					}
265					break;
266				case 2: // stop bits
267					if (message->what == B_GET_PROPERTY) {
268						reply.AddInt32("result", fSerialPort.StopBits() + 1);
269						message->SendReply(&reply);
270						return;
271					}
272					if (message->what == B_SET_PROPERTY) {
273						int32 bits = message->FindInt32("data");
274						settingsChanged = true;
275						settings.AddInt32("stopbits", bits - 1);
276					}
277					break;
278				case 3: // parity
279				{
280					static const char* strings[] = {"none", "odd", "even"};
281					if (message->what == B_GET_PROPERTY) {
282						reply.AddString("result",
283							strings[fSerialPort.ParityMode()]);
284						message->SendReply(&reply);
285						return;
286					}
287					if (message->what == B_SET_PROPERTY) {
288						BString bits = message->FindString("data");
289						int i;
290						for (i = 0; i < 3; i++) {
291							if (bits == strings[i])
292								break;
293						}
294
295						if (i < 3) {
296							settingsChanged = true;
297							settings.AddInt32("parity", i);
298						}
299					}
300					break;
301				}
302				case 4: // flow control
303				{
304					static const char* strings[] = {"none", "hardware",
305						"software", "both"};
306					if (message->what == B_GET_PROPERTY) {
307						reply.AddString("result",
308							strings[fSerialPort.FlowControl()]);
309						message->SendReply(&reply);
310						return;
311					}
312					if (message->what == B_SET_PROPERTY) {
313						BString bits = message->FindString("data");
314						int i;
315						for (i = 0; i < 4; i++) {
316							if (bits == strings[i])
317								break;
318						}
319
320						if (i < 4) {
321							settingsChanged = true;
322							settings.AddInt32("flowcontrol", i);
323						}
324					}
325					break;
326				}
327				case 5: // port
328					if (message->what == B_GET_PROPERTY) {
329						reply.AddString("port", GetPort());
330						message->SendReply(&reply);
331					} else if (message->what == B_DELETE_PROPERTY
332						|| message->what == B_SET_PROPERTY) {
333						BString path = message->FindString("data");
334						BMessage openMessage(kMsgOpenPort);
335						openMessage.AddString("port name", path);
336						PostMessage(&openMessage);
337						fWindow->PostMessage(&openMessage);
338					}
339					return;
340			}
341		}
342
343		if (settingsChanged) {
344			PostMessage(&settings);
345			fWindow->PostMessage(&settings);
346			return;
347		}
348	}
349
350	BApplication::MessageReceived(message);
351}
352
353
354bool SerialApp::QuitRequested()
355{
356	if (BApplication::QuitRequested()) {
357		SaveSettings();
358		return true;
359	}
360	return false;
361}
362
363
364const BString& SerialApp::GetPort()
365{
366	return fPortPath;
367}
368
369
370void SerialApp::LoadSettings()
371{
372	BPath path;
373	find_directory(B_USER_SETTINGS_DIRECTORY, &path);
374	path.Append("SerialConnect");
375
376	BFile file(path.Path(), B_READ_ONLY);
377	BMessage message(kMsgSettings);
378	if (message.Unflatten(&file) != B_OK) {
379		message.AddInt32("parity", fSerialPort.ParityMode());
380		message.AddInt32("databits", fSerialPort.DataBits());
381		message.AddInt32("stopbits", fSerialPort.StopBits());
382		message.AddInt32("baudrate", fSerialPort.DataRate());
383		message.AddInt32("flowcontrol", fSerialPort.FlowControl());
384	}
385
386	be_app->PostMessage(&message);
387	fWindow->PostMessage(&message);
388}
389
390
391void SerialApp::SaveSettings()
392{
393	BMessage message(kMsgSettings);
394	message.AddInt32("parity", fSerialPort.ParityMode());
395	message.AddInt32("databits", fSerialPort.DataBits());
396	message.AddInt32("stopbits", fSerialPort.StopBits());
397	message.AddInt32("baudrate", fSerialPort.DataRate());
398	message.AddInt32("flowcontrol", fSerialPort.FlowControl());
399
400	BPath path;
401	find_directory(B_USER_SETTINGS_DIRECTORY, &path);
402	path.Append("SerialConnect");
403
404	BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE);
405	message.Flatten(&file);
406}
407
408
409/* static */
410status_t SerialApp::PollSerial(void*)
411{
412	SerialApp* application = (SerialApp*)be_app;
413	char buffer[256];
414
415	for (;;) {
416		ssize_t bytesRead;
417
418		bytesRead = application->fSerialPort.Read(buffer, sizeof(buffer));
419		if (bytesRead == B_FILE_ERROR) {
420			// Port is not open - wait for it and start over
421			acquire_sem(application->fSerialLock);
422		} else if (bytesRead > 0) {
423			// We read something, forward it to the app for handling
424			BMessage* serialData = new BMessage(kMsgDataRead);
425			serialData->AddData("data", B_RAW_TYPE, buffer, bytesRead);
426			be_app_messenger.SendMessage(serialData);
427		}
428	}
429
430	// Should not reach this line anyway...
431	return B_OK;
432}
433
434
435const char* SerialApp::kApplicationSignature
436	= "application/x-vnd.haiku.SerialConnect";
437
438
439int main(int argc, char** argv)
440{
441	SerialApp app;
442	app.Run();
443}
444
445
446status_t
447SerialApp::GetSupportedSuites(BMessage* message)
448{
449	message->AddString("suites", "suite/vnd.Haiku-SerialPort");
450	message->AddFlat("messages", &kScriptingProperties);
451	return BApplication::GetSupportedSuites(message);
452}
453
454
455BHandler*
456SerialApp::ResolveSpecifier(BMessage* message, int32 index,
457	BMessage* specifier, int32 what, const char* property)
458{
459	if (kScriptingProperties.FindMatch(message, index, specifier, what,
460		property) >= 0)
461		return this;
462
463	return BApplication::ResolveSpecifier(message, index, specifier, what,
464		property);
465}
466