-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathxn.h
323 lines (269 loc) · 9.81 KB
/
xn.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
#ifndef XN_H
#define XN_H
/*
This file implements a low-level XpressNet class, which allows PC to
communicate with XpressNET command station over virtual serial port.
A command from PC to Command station is send by calling any of the public
functions listed below. You may pass an 'ok' and an 'error' callback when
calling this function. Exactly one of these callbacks is *guaranteed* to be
called based on the response from command station or LI.
How does sending work?
(1) User calls a function.
(2) Data are sent to the asynchronous serial port.
(3) The function ends.
(4a) When the command station sends a proper reply, 'ok' callback is called.
(4b) When the command station sends no reply or improper reply, the command
is sent again. Iff the command station does not reply for _PENDING_SEND_MAX
times, 'error' callback is called.
* Response to the user`s command is transmitted to the user as callback.
* General callbacks are implemented as slots (see below).
* For adding more commands, see xn-typedefs.h.
*/
#include <QDateTime>
#include <QObject>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QTimer>
#include <memory>
#include <queue>
#include <vector>
#include "q-str-exception.h"
#include "xn-commands.h"
#include "xn-loco-addr.h"
#define XN_VERSION_MAJOR 2
#define XN_VERSION_MINOR 7
namespace Xn {
constexpr size_t _PENDING_CHECK_INTERVAL = 100; // ms
constexpr size_t _PENDING_TIMEOUT = 1000; // ms
constexpr size_t _PENDING_PROG_TIMEOUT = 10000; // 10 s
constexpr size_t _PENDING_SEND_MAX = 3; // how many times to send the command till error
constexpr size_t _PENDING_MAX_AT_ONCE = 3; // how many commands could be pending at once
constexpr size_t _BUF_IN_TIMEOUT = 300; // ms
constexpr size_t _STEPS_CNT = 28;
constexpr size_t _OUT_TIMER_INTERVAL_DEFAULT = 50; // ms
constexpr size_t _OUT_TIMER_INTERVAL_MIN = 50; // ms
constexpr size_t _OUT_TIMER_INTERVAL_MAX = 500; // ms
struct EOpenError : public QStrException {
EOpenError(const QString str) : QStrException(str) {}
};
struct EWriteError : public QStrException {
EWriteError(const QString str) : QStrException(str) {}
};
struct EInvalidTrkStatus : public QStrException {
EInvalidTrkStatus(const QString str) : QStrException(str) {}
};
struct EUnsupportedInterface : public QStrException {
EUnsupportedInterface(const QString str) : QStrException(str) {}
};
struct EInvalidConfig : public QStrException {
EInvalidConfig(const QString str) : QStrException(str) {}
};
enum class LIType {
LI100,
LI101,
uLI,
LIUSBEth,
};
LIType liInterface(const QString &name);
QString liInterfaceName(const LIType &type);
enum class TrkStatus {
Unknown = 0,
Off = 1,
On = 2,
Programming = 3,
};
using CommandCallbackFunc = std::function<void(void *sender, void *data)>;
struct CommandCallback {
CommandCallbackFunc const func;
void *const data;
CommandCallback(CommandCallbackFunc const func, void *const data = nullptr)
: func(func), data(data) {}
};
using Cb = CommandCallback;
using UPCb = std::unique_ptr<CommandCallback>;
// PendingItem represents a command sent to the LI, for which the response
// has not arrived yet.
struct PendingItem {
PendingItem(std::unique_ptr<const Cmd> &cmd, QDateTime timeout, size_t no_sent,
std::unique_ptr<Cb> &&callback_ok, std::unique_ptr<Cb> &&callback_err)
: cmd(std::move(cmd))
, timeout(timeout)
, no_sent(no_sent)
, callback_ok(std::move(callback_ok))
, callback_err(std::move(callback_err)) {}
PendingItem(PendingItem &&pending) noexcept
: cmd(std::move(pending.cmd))
, timeout(pending.timeout)
, no_sent(pending.no_sent)
, callback_ok(std::move(pending.callback_ok))
, callback_err(std::move(pending.callback_err)) {}
std::unique_ptr<const Cmd> cmd;
QDateTime timeout;
size_t no_sent;
std::unique_ptr<Cb> callback_ok;
std::unique_ptr<Cb> callback_err;
};
enum class LogLevel {
None = 0,
Error = 1,
Warning = 2,
Info = 3,
Commands = 4,
RawData = 5,
Debug = 6,
};
enum class FeedbackType {
accWithoutFb = 0,
accWithFb = 1,
fb = 2,
reserved = 3,
};
union AccInputsState {
uint8_t all;
struct {
bool i0 : 1;
bool i1 : 1;
bool i2 : 1;
bool i3 : 1;
} sep;
};
enum class RecvCmdType {
LiError = 0x01,
LiVersion = 0x02,
LiSettings = 0xF2,
CsGeneralEvent = 0x61,
CsStatus = 0x62,
CsX63 = 0x63,
CsLocoInfo = 0xE4,
CsLocoFunc = 0xE3,
CsFeedbackBroadcast = 0x40, // actually any [0x40, 0x42, 0x44, ... 0x4E], see specs
CsAccInfoResp = 0x42,
};
struct XNConfig {
size_t outInterval = _OUT_TIMER_INTERVAL_DEFAULT;
};
class XpressNet : public QObject {
Q_OBJECT
public:
LogLevel loglevel = LogLevel::None;
static constexpr unsigned _VERSION_MAJOR = XN_VERSION_MAJOR;
static constexpr unsigned _VERSION_MINOR = XN_VERSION_MINOR;
XpressNet(QObject *parent = nullptr);
~XpressNet() override;
void connect(const QString &portname, int32_t br, QSerialPort::FlowControl fc, LIType liType);
void disconnect();
bool connected() const;
TrkStatus getTrkStatus() const;
void setTrkStatus(TrkStatus, UPCb ok = nullptr, UPCb err = nullptr);
void emergencyStop(LocoAddr, UPCb ok = nullptr, UPCb err = nullptr);
void emergencyStop(UPCb ok = nullptr, UPCb err = nullptr);
void getCommandStationVersion(GotCSVersion const &, UPCb err = nullptr);
void getCommandStationStatus(UPCb ok = nullptr, UPCb err = nullptr);
void getLIVersion(GotLIVersion const &, UPCb err = nullptr);
void getLIAddress(GotLIAddress const &, UPCb err = nullptr);
void setLIAddress(uint8_t addr, UPCb ok = nullptr, UPCb err = nullptr);
void pomWriteCv(LocoAddr, uint16_t cv, uint8_t value, UPCb ok = nullptr, UPCb err = nullptr);
void pomWriteBit(LocoAddr, uint16_t cv, uint8_t biti, bool value, UPCb ok = nullptr,
UPCb err = nullptr);
void readCVdirect(uint8_t cv, ReadCV const &callback, UPCb err = nullptr);
void writeCVdirect(uint8_t cv, uint8_t value, UPCb ok = nullptr, UPCb err = nullptr);
void setSpeed(LocoAddr, uint8_t speed, Direction direction, UPCb ok = nullptr,
UPCb err = nullptr);
void getLocoInfo(LocoAddr, GotLocoInfo const &, UPCb err = nullptr);
void getLocoFunc1328(LocoAddr, GotLocoFunc1328, UPCb err = nullptr);
void setFuncA(LocoAddr, FA, UPCb ok = nullptr, UPCb err = nullptr);
void setFuncB(LocoAddr, FB, FSet, UPCb ok = nullptr, UPCb err = nullptr);
void setFuncC(LocoAddr, FC, UPCb ok = nullptr, UPCb err = nullptr);
void setFuncD(LocoAddr, FD, UPCb ok = nullptr, UPCb err = nullptr);
void accInfoRequest(uint8_t groupAddr, bool nibble, UPCb err = nullptr);
void accOpRequest(uint16_t portAddr, bool state, // portAddr 0-2047
UPCb ok = nullptr, UPCb err = nullptr);
void pendingClear();
static QString xnReadCVStatusToQString(ReadCVStatus st);
static std::vector<QSerialPortInfo> ports(LIType);
LIType liType() const;
XNConfig config() const;
void setConfig(XNConfig config);
private slots:
void handleReadyRead();
void handleError(QSerialPort::SerialPortError);
void m_pending_timer_tick();
void m_out_timer_tick();
void sp_about_to_close();
signals:
void onError(QString error);
void onLog(QString message, Xn::LogLevel loglevel);
void onConnect();
void onDisconnect();
void onTrkStatusChanged(Xn::TrkStatus);
void onLocoStolen(Xn::LocoAddr);
void onAccInputChanged(uint8_t groupAddr, bool nibble, bool error, Xn::FeedbackType inputType,
Xn::AccInputsState state);
private:
QSerialPort m_serialPort;
QByteArray m_readData;
QDateTime m_receiveTimeout;
QDateTime m_lastSent;
std::deque<PendingItem> m_pending; // commands sent to CS with no response yet
std::deque<PendingItem> m_out; // commands not sent to CS yet
QTimer m_pending_timer;
QTimer m_out_timer;
TrkStatus m_trk_status = TrkStatus::Unknown;
LIType m_liType;
XNConfig m_config;
using MsgType = std::vector<uint8_t>;
void parseMessage(MsgType &msg);
void send(MsgType);
void send(std::unique_ptr<const Cmd>, UPCb ok = nullptr, UPCb err = nullptr,
size_t no_sent = 1);
void to_send(PendingItem &&, bool bypass_m_out_emptiness = false);
void to_send(std::unique_ptr<const Cmd> &, UPCb ok = nullptr, UPCb err = nullptr,
size_t no_sent = 1, bool bypass_m_out_emptiness = false);
template <typename T>
void to_send(const T &&cmd, UPCb ok = nullptr, UPCb err = nullptr);
void handleMsgLiError(MsgType &msg);
void handleMsgLiVersion(MsgType &msg);
void handleMsgCsGeneralEvent(MsgType &msg);
void handleMsgCsStatus(MsgType &msg);
void handleMsgCsVersion(MsgType &msg);
void handleMsgCvRead(MsgType &msg);
void handleMsgLocoInfo(MsgType &msg);
void handleMsgLocoFunc(MsgType &msg);
void handleMsgLIAddr(MsgType &msg);
void handleMsgAcc(MsgType &msg);
void pending_ok();
void pending_err(bool _log = true);
void pending_send();
void send_next_out();
void log(const QString &message, LogLevel loglevel);
QDateTime timeout(const Cmd *x);
bool liAcknowledgesSetAccState() const;
bool conflictWithPending(const Cmd &) const;
bool conflictWithOut(const Cmd &) const;
template <typename DataT, typename ItemType>
QString dataToStr(DataT, size_t len = 0);
template <typename Target>
bool is(const PendingItem &h);
};
// Templated functions must be in header file to compile
template <typename T>
void XpressNet::to_send(const T &&cmd, UPCb ok, UPCb err) {
std::unique_ptr<const Cmd> cmd2(std::make_unique<const T>(cmd));
to_send(cmd2, std::move(ok), std::move(err));
}
template <typename DataT, typename ItemType>
QString XpressNet::dataToStr(DataT data, size_t len) {
QString out;
size_t i = 0;
for (auto d = data.begin(); (d != data.end() && (len == 0 || i < len)); d++, i++)
out += "0x" +
QString("%1 ").arg(static_cast<ItemType>(*d), 2, 16, QLatin1Char('0')).toUpper();
return out.trimmed();
}
template <typename Target>
bool XpressNet::is(const PendingItem &h) {
return (dynamic_cast<const Target *>(h.cmd.get()) != nullptr);
}
QString flowControlToStr(QSerialPort::FlowControl);
} // namespace Xn
#endif