Browse Source

Basic script support for serial port communications

Neal Wilson 10 years ago
parent
commit
3c2e70f796
2 changed files with 224 additions and 32 deletions
  1. 139
    0
      src/typica.w
  2. 85
    32
      src/unsupportedserial.w

+ 139
- 0
src/typica.w View File

679
 	return self;
679
 	return self;
680
 }
680
 }
681
 
681
 
682
+template<> QByteArray getself(QScriptContext *context)
683
+{
684
+	QByteArray self = context->thisObject().toVariant().toByteArray();
685
+	return self;
686
+}
687
+
682
 template<> SqlQueryConnection* getself(QScriptContext *context)
688
 template<> SqlQueryConnection* getself(QScriptContext *context)
683
 {
689
 {
684
 	SqlQueryConnection *self =@|
690
 	SqlQueryConnection *self =@|
754
 	return (Units::Unit)(context->argument(arg).toInt32());
760
 	return (Units::Unit)(context->argument(arg).toInt32());
755
 }
761
 }
756
 
762
 
763
+template<> QByteArray argument(int arg, QScriptContext *context)
764
+{
765
+	return qscriptvalue_cast<QByteArray>(context->argument(arg));
766
+}
767
+
757
 @ The scripting engine is informed of a number of classes defined elsewhere in
768
 @ The scripting engine is informed of a number of classes defined elsewhere in
758
 the program. Code related to scripting these classes is grouped with the code
769
 the program. Code related to scripting these classes is grouped with the code
759
 implementing the classes. Additionally, there are several classes from Qt which
770
 implementing the classes. Additionally, there are several classes from Qt which
1858
 QScriptValue QIODevice_close(QScriptContext *context, QScriptEngine *engine);
1869
 QScriptValue QIODevice_close(QScriptContext *context, QScriptEngine *engine);
1859
 QScriptValue QIODevice_readToString(QScriptContext *context,
1870
 QScriptValue QIODevice_readToString(QScriptContext *context,
1860
                                     QScriptEngine *engine);
1871
                                     QScriptEngine *engine);
1872
+QScriptValue QIODevice_putChar(QScriptContext *context, QScriptEngine *engine);
1873
+QScriptValue QIODevice_writeString(QScriptContext *context, QScriptEngine *engine);
1874
+QScriptValue QIODevice_writeBytes(QScriptContext *context, QScriptEngine *engine);
1861
 
1875
 
1862
 @ This function is passed to the scripting engine.
1876
 @ This function is passed to the scripting engine.
1863
 
1877
 
1909
 	value.setProperty("close", engine->newFunction(QIODevice_close));
1923
 	value.setProperty("close", engine->newFunction(QIODevice_close));
1910
 	value.setProperty("readToString",
1924
 	value.setProperty("readToString",
1911
 	                  engine->newFunction(QIODevice_readToString));
1925
 	                  engine->newFunction(QIODevice_readToString));
1926
+	value.setProperty("putChar", engine->newFunction(QIODevice_putChar));
1927
+	value.setProperty("writeString", engine->newFunction(QIODevice_writeString));
1928
+	value.setProperty("writeBytes", engine->newFunction(QIODevice_writeBytes));
1912
 }
1929
 }
1913
 
1930
 
1914
 @ These are simple wrappers. In the case of the |open()| property, one argument
1931
 @ These are simple wrappers. In the case of the |open()| property, one argument
1963
 	return QScriptValue(QString(self->readAll()));
1980
 	return QScriptValue(QString(self->readAll()));
1964
 }
1981
 }
1965
 
1982
 
1983
+@ In support of serial port communications, wrappers around two methods for
1984
+writing data have been added. As these are valid for other classes derived from
1985
+|QIODevice|, they are added here so the functionality is available more
1986
+broadly.
1987
+
1988
+As we are unable to pass a type that guarantees only a single character, we
1989
+instead accept a string and only pass along the first character.
1990
+
1991
+@<Functions for scripting@>=
1992
+QScriptValue QIODevice_putChar(QScriptContext *context, QScriptEngine *)
1993
+{
1994
+	QIODevice *self = getself<QIODevice *>(context);
1995
+	if(context->argumentCount() == 1)
1996
+	{
1997
+		return QScriptValue(self->putChar(argument<QString>(0, context).toUtf8().at(0)));
1998
+	}
1999
+	context->throwError("Incorrect number of arguments passed to "@|
2000
+	                    "QIODevice::putChar()");
2001
+	return QScriptValue();
2002
+}
2003
+
2004
+@ Two wrappers are provided around |QIODevice::write()| for outputting
2005
+multi-byte data. If we are writing strings that are valid UTF-8, we can use the
2006
+|writeString| wrapper, but if we require full control over exactly which bytes
2007
+are output, the |writeBytes| wrapper is more appropriate.
2008
+
2009
+@<Functions for scripting@>=
2010
+QScriptValue QIODevice_writeString(QScriptContext *context, QScriptEngine *)
2011
+{
2012
+	QIODevice *self = getself<QIODevice *>(context);
2013
+	if(context->argumentCount() == 1)
2014
+	{
2015
+		self->write(argument<QString>(0, context).toUtf8());
2016
+	}
2017
+	else
2018
+	{
2019
+		context->throwError("Incorrect number of arguments passed to "@|
2020
+	                        "QIODevice::writeString()");
2021
+	}
2022
+	return QScriptValue();
2023
+}
2024
+
2025
+QScriptValue QIODevice_writeBytes(QScriptContext *context, QScriptEngine *)
2026
+{
2027
+	QIODevice *self = getself<QIODevice *>(context);
2028
+	if(context->argumentCount() == 1)
2029
+	{
2030
+		self->write(argument<QByteArray>(0, context));
2031
+	}
2032
+	else
2033
+	{
2034
+		context->throwError("Incorrect number of arguments passed to "@|
2035
+	                        "QIODevice::writeBytes()");
2036
+	}
2037
+	return QScriptValue();
2038
+}
2039
+
2040
+@ In order to work with |QByteArray| this should also be exposed to the host
2041
+environment.
2042
+
2043
+@<Function prototypes for scripting@>=
2044
+QScriptValue QByteArray_toScriptValue(QScriptEngine *engine, const QByteArray &bytes);
2045
+void QByteArray_fromScriptValue(const QScriptValue &value, QByteArray &bytes);
2046
+QScriptValue constructQByteArray(QScriptContext *context, QScriptEngine *engine);
2047
+void setQByteArrayProperties(QScriptValue value, QScriptEngine *engine);
2048
+QScriptValue QByteArray_fromHex(QScriptContext *context, QScriptEngine *engine);
2049
+
2050
+@ First, we provide some functionns for moving array data across the
2051
+language barrier.
2052
+
2053
+@<Functions for scripting@>=
2054
+QScriptValue QByteArray_toScriptValue(QScriptEngine *engine, const QByteArray &bytes)
2055
+{
2056
+	QScriptValue object = engine->newVariant(QVariant(bytes));
2057
+	setQByteArrayProperties(object, engine);
2058
+	return object;
2059
+}
2060
+
2061
+void QByteArray_fromScriptValue(const QScriptValue &value, QByteArray &bytes)
2062
+{
2063
+	bytes = value.toVariant().toByteArray();
2064
+}
2065
+
2066
+@ We register this our conversion functions and allow creation of new arrays
2067
+next.
2068
+
2069
+@<Set up the scripting engine@>=
2070
+qScriptRegisterMetaType(engine, QByteArray_toScriptValue, QByteArray_fromScriptValue);
2071
+constructor = engine->newFunction(constructQByteArray);
2072
+engine->globalObject().setProperty("QByteArray", constructor);
2073
+
2074
+@ The constructor is straightforward.
2075
+
2076
+@<Functions for scripting@>=
2077
+QScriptValue constructQByteArray(QScriptContext *, QScriptEngine *engine)
2078
+{
2079
+	QScriptValue object = engine->toScriptValue<QByteArray>(QByteArray());
2080
+	setQByteArrayProperties(object, engine);
2081
+	return object;
2082
+}
2083
+
2084
+@ There are many methods which are not automatically available which we may
2085
+want to have wrappers around. These should be added as required.
2086
+
2087
+@<Functions for scripting@>=
2088
+void setQByteArrayProperties(QScriptValue value, QScriptEngine *engine)
2089
+{
2090
+	value.setProperty("fromHex", engine->newFunction(QByteArray_fromHex));
2091
+}
2092
+
2093
+@ Perhaps the easiest way to deal with fixed byte strings for serial
2094
+communications across script boundaries is to use a hex encoded string.
2095
+
2096
+@<Functions for scripting@>=
2097
+QScriptValue QByteArray_fromHex(QScriptContext *context, QScriptEngine *engine)
2098
+{
2099
+	QByteArray self = getself<QByteArray>(context);
2100
+	QByteArray retval;
2101
+	retval = self.fromHex(argument<QString>(0, context).toUtf8());
2102
+	return engine->toScriptValue<QByteArray>(retval);
2103
+}
2104
+
1966
 @* Scripting QBuffer.
2105
 @* Scripting QBuffer.
1967
 
2106
 
1968
 \noindent Sometimes it is desirable to load a roast profile from a file. At
2107
 \noindent Sometimes it is desirable to load a roast profile from a file. At

+ 85
- 32
src/unsupportedserial.w View File

1
-@** Unsupported Serial Port Devices.
2
-
3
-\noindent There are many data acquisition products which connect over or
4
-present themselves as a serial port and it has become relatively easy for
5
-hardware tinkerers to develop new devices matching this description. In this
6
-space there is no apparent consideration given to interoperability and with
7
-some devices the communications protocol even changes significantly between
8
-firmware revisions. There are far too many devices like this to support
9
-everything. At the same time, there are people who have these devices, are
10
-capable of programming the basic communications handling, but have difficulty
11
-with all of the supporting code that must be written to properly integrate with
12
-the rest of \pn. By providing an in-program environment that handles much of
13
-the boilerplate and allowing people to write scripts implementing these
14
-protocols without the need to modify core \pn code or recompile the program,
15
-these people may find it easier to make their existing hardware work. Such
16
-scripts can also serve as prototypes for later integration.
17
-
18
-It is common among these devices that port settings aside from the port name
19
-are fixed, but there may be other characteristics that are configurable on a
20
-per-device or per-channel basis. As it is impossible to know what these
21
-details might be, the configuration widgets for these devices and channels
22
-simply provide space to provide key value pairs which are provided to the host
23
-environment. Common configurations will have a device node representing a
24
-single logical device, usually a single physical device but this is not in any
25
-way enforced, and one child node per channel, the details of which are made
26
-available to the device script and are also used to integrate with the logging
27
-view.
28
-
29
-The use of the word serial should be considered legacy of the initial
30
-conception of this feature. The implementation here is sufficiently generic
31
-that there is no reason this could not be extended to cover devices that are
1
+@** Script Driven Devices.
2
+
3
+\noindent There are many data acquisition products that are reasonable to use
4
+with \pn which are not natively supported due to lack of available hardware
5
+for testing, lack of time or money to develop that support, or lack of
6
+documentation. It has also become relatively simple for hardware tinkerers to
7
+develop new devices matching this description as well. Vendors in this space
8
+tend to give inadequate consideration to interoperability and with some devices
9
+the communications protocol used changes significantly between firmware
10
+revisions. There are simply far too many devices like this to support
11
+everything. At the same time there are people with these devices who are
12
+capable of programming the basic communications handling but have difficulty
13
+with integrating that with \pn. By providing an in-program environment that
14
+handles much of the boilerplate and allowing people to write scripts
15
+implementing these protocols without the need to modify core \pn code or
16
+recompile the program, these people may find it easier to make their existing
17
+hardware work. Such scripts can also serve as prototypes for native support.
18
+
19
+Configuration widgets for these devices allow key value pairs to be specified
20
+both at the device level and on a per-channel basis. This is intentionally kept
21
+generic as it is impossible to know what configurable details may be required.
22
+Common configurations will have a device node representing a single logical
23
+device, usually a single physical device but this is not in any way enforced,
24
+and one child node per channel. These details are made available to the device
25
+script and are used to integrate with the logging view.
26
+
27
+Some of the naming conventions used here are legacy of the initial conception
28
+of this feature and should be changed before release if there is time to do so.
29
+While initial support will be focused on devices that present as a serial port,
30
+there is no reason this could not be extended to cover devices that are
32
 interfaced through USB HID, Bluetooth, COM, output piped from an external
31
 interfaced through USB HID, Bluetooth, COM, output piped from an external
33
 console program, devices interfaced through arbitrary libraries, or any other
32
 console program, devices interfaced through arbitrary libraries, or any other
34
 class of device not directly supported in the core code should there be an
33
 class of device not directly supported in the core code should there be an
35
-interest in these.
34
+interest in any of these.
36
 
35
 
37
 @<Class declarations@>=
36
 @<Class declarations@>=
38
 class UnsupportedSerialDeviceConfWidget : public BasicDeviceConfigurationWidget
37
 class UnsupportedSerialDeviceConfWidget : public BasicDeviceConfigurationWidget
639
 @<Class implementations@>=
638
 @<Class implementations@>=
640
 @<UnsupportedSerialDeviceConfWidget implementation@>
639
 @<UnsupportedSerialDeviceConfWidget implementation@>
641
 @<JavaScriptDevice implementation@>
640
 @<JavaScriptDevice implementation@>
641
+
642
+@* Serial Ports.
643
+
644
+\noindent The first use case for script driven devices was connecting to
645
+devices which present themselves as a serial port. This covers a broad range
646
+of data acquisition products. To provide this support, |QextSerialPort|, which
647
+was already used to support some other hardware options, is directly exposed to
648
+the host environment.
649
+
650
+@<Function prototypes for scripting@>=
651
+QScriptValue constructSerialPort(QScriptContext *context, QScriptEngine *engine);
652
+void setSerialPortProperties(QScriptValue value, QScriptEngine *engine);
653
+QScriptValue SerialPort_flush(QScriptContext *context, QScriptEngine *engine);
654
+
655
+@ Our constructor is passed to the scripting engine.
656
+
657
+@<Set up the scripting engine@>=
658
+constructor = engine->newFunction(constructSerialPort);
659
+value = engine->newQMetaObject(&QextSerialPort::staticMetaObject, constructor);
660
+engine->globalObject().setProperty("SerialPort", value);
661
+
662
+@ At present we only support event driven communications and are not passing
663
+any port settings through the constructor. Such functionality may be added in
664
+the future, but it is not strictly necessary.
665
+
666
+@<Functions for scripting@>=
667
+QScriptValue constructSerialPort(QScriptContext *, QScriptEngine *engine)
668
+{
669
+	QScriptValue object = engine->newQObject(new QextSerialPort());
670
+	setSerialPortProperties(object, engine);
671
+	return object;
672
+}
673
+
674
+@ Some properties of |QIODevice| are brought in as usual for similar subclasses
675
+but we also add a wrapper around the |flush()| method.
676
+
677
+@<Functions for scripting@>=
678
+void setSerialPortProperties(QScriptValue value, QScriptEngine *engine)
679
+{
680
+	setQIODeviceProperties(value, engine);
681
+	value.setProperty("flush", engine->newFunction(SerialPort_flush));
682
+}
683
+
684
+@ The wrapper around |flush()| is trivial.
685
+
686
+@<Functions for scripting@>=
687
+QScriptValue SerialPort_flush(QScriptContext *context, QScriptEngine *)
688
+{
689
+	QextSerialPort *self = getself<QextSerialPort *>(context);
690
+	self->flush();
691
+	return QScriptValue();
692
+}
693
+
694
+

Loading…
Cancel
Save