aboutsummaryrefslogtreecommitdiff
path: root/Marlin/src/core/serial_hook.h
blob: 45e64d77933989d81d64c71b1aeb021b5f9aa8a6 (plain) (blame)
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
/**
 * Marlin 3D Printer Firmware
 * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
 *
 * Based on Sprinter and grbl.
 * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */
#pragma once

#include "serial_base.h"

// The most basic serial class: it dispatch to the base serial class with no hook whatsoever. This will compile to nothing but the base serial class
template <class SerialT>
struct BaseSerial : public SerialBase< BaseSerial<SerialT> >, public SerialT {
  typedef SerialBase< BaseSerial<SerialT> > BaseClassT;

  // It's required to implement a write method here to help compiler disambiguate what method to call
  using SerialT::write;
  using SerialT::flush;

  void msgDone() {}

  bool available(uint8_t index) { return index == 0 && SerialT::available(); }
  int read(uint8_t index)       { return index == 0 ? SerialT::read() : -1; }
  bool connected()              { return CALL_IF_EXISTS(bool, static_cast<SerialT*>(this), connected);; }
  // We have 2 implementation of the same method in both base class, let's say which one we want
  using SerialT::available;
  using SerialT::read;
  using SerialT::begin;
  using SerialT::end;

  using BaseClassT::print;
  using BaseClassT::println;

  BaseSerial(const bool e) : BaseClassT(e) {}

  // Forward constructor
  template <typename... Args>
  BaseSerial(const bool e, Args... args) : BaseClassT(e), SerialT(args...) {}
};

// A serial with a condition checked at runtime for its output
// A bit less efficient than static dispatching but since it's only used for ethernet's serial output right now, it's ok.
template <class SerialT>
struct ConditionalSerial : public SerialBase< ConditionalSerial<SerialT> > {
  typedef SerialBase< ConditionalSerial<SerialT> > BaseClassT;

  bool    & condition;
  SerialT & out;
  NO_INLINE size_t write(uint8_t c) { if (condition) return out.write(c); return 0; }
  void flush()            { if (condition) out.flush();  }
  void begin(long br)     { out.begin(br); }
  void end()              { out.end(); }

  void msgDone() {}
  bool connected()              { return CALL_IF_EXISTS(bool, &out, connected); }

  bool available(uint8_t index) { return index == 0 && out.available(); }
  int read(uint8_t index)       { return index == 0 ? out.read() : -1; }
  using BaseClassT::available;
  using BaseClassT::read;

  ConditionalSerial(bool & conditionVariable, SerialT & out, const bool e) : BaseClassT(e), condition(conditionVariable), out(out) {}
};

// A simple foward class that taking a reference to an existing serial instance (likely created in their respective framework)
template <class SerialT>
struct ForwardSerial : public SerialBase< ForwardSerial<SerialT> > {
  typedef SerialBase< ForwardSerial<SerialT> > BaseClassT;

  SerialT & out;
  NO_INLINE size_t write(uint8_t c) { return out.write(c); }
  void flush()            { out.flush();  }
  void begin(long br)     { out.begin(br); }
  void end()              { out.end(); }

  void msgDone() {}
  // Existing instances implement Arduino's operator bool, so use that if it's available
  bool connected()              { return Private::HasMember_connected<SerialT>::value ? CALL_IF_EXISTS(bool, &out, connected) : (bool)out; }

  bool available(uint8_t index) { return index == 0 && out.available(); }
  int read(uint8_t index)       { return index == 0 ? out.read() : -1; }
  bool available()              { return out.available(); }
  int read()                    { return out.read(); }

  ForwardSerial(const bool e, SerialT & out) : BaseClassT(e), out(out) {}
};

// A class that's can be hooked and unhooked at runtime, useful to capturing the output of the serial interface
template <class SerialT>
struct RuntimeSerial : public SerialBase< RuntimeSerial<SerialT> >, public SerialT {
  typedef SerialBase< RuntimeSerial<SerialT> > BaseClassT;
  typedef void (*WriteHook)(void * userPointer, uint8_t c);
  typedef void (*EndOfMessageHook)(void * userPointer);

  WriteHook        writeHook;
  EndOfMessageHook eofHook;
  void *           userPointer;

  NO_INLINE size_t write(uint8_t c) {
    if (writeHook) writeHook(userPointer, c);
    return SerialT::write(c);
  }

  NO_INLINE void msgDone() {
    if (eofHook) eofHook(userPointer);
  }

  bool available(uint8_t index) { return index == 0 && SerialT::available(); }
  int read(uint8_t index)       { return index == 0 ? SerialT::read() : -1; }
  using SerialT::available;
  using SerialT::read;
  using SerialT::flush;
  using SerialT::begin;
  using SerialT::end;

  using BaseClassT::print;
  using BaseClassT::println;


  // Underlying implementation might use Arduino's bool operator
  bool connected() {
    return Private::HasMember_connected<SerialT>::value ? CALL_IF_EXISTS(bool, static_cast<SerialT*>(this), connected) : static_cast<SerialT*>(this)->operator bool();
  }

  void setHook(WriteHook writeHook = 0, EndOfMessageHook eofHook = 0, void * userPointer = 0) {
    // Order is important here as serial code can be called inside interrupts
    // When setting a hook, the user pointer must be set first so if writeHook is called as soon as it's set, it'll be valid
    if (userPointer) this->userPointer = userPointer;
    this->writeHook = writeHook;
    this->eofHook = eofHook;
    // Order is important here because of asynchronous access here
    // When unsetting a hook, the user pointer must be unset last so that any pending writeHook is still using the old pointer
    if (!userPointer) this->userPointer = 0;
  }

  RuntimeSerial(const bool e) : BaseClassT(e), writeHook(0), eofHook(0), userPointer(0) {}

  // Forward constructor
  template <typename... Args>
  RuntimeSerial(const bool e, Args... args) : BaseClassT(e), SerialT(args...) {}
};

// A class that's duplicating its output conditionally to 2 serial interface
template <class Serial0T, class Serial1T, const uint8_t offset = 0>
struct MultiSerial : public SerialBase< MultiSerial<Serial0T, Serial1T, offset> > {
  typedef SerialBase< MultiSerial<Serial0T, Serial1T, offset> > BaseClassT;

  uint8_t    portMask;
  Serial0T & serial0;
  Serial1T & serial1;

  enum Masks {
    FirstOutputMask   =  (1 << offset),
    SecondOutputMask  =  (1 << (offset + 1)),
    AllMask           = FirstOutputMask | SecondOutputMask,
  };

  NO_INLINE size_t write(uint8_t c) {
    size_t ret = 0;
    if (portMask & FirstOutputMask)   ret = serial0.write(c);
    if (portMask & SecondOutputMask)  ret = serial1.write(c) | ret;
    return ret;
  }
  NO_INLINE void msgDone() {
    if (portMask & FirstOutputMask)   serial0.msgDone();
    if (portMask & SecondOutputMask)  serial1.msgDone();
  }
  bool available(uint8_t index) {
    switch(index) {
      case 0 + offset: return serial0.available();
      case 1 + offset: return serial1.available();
      default: return false;
    }
  }
  NO_INLINE int read(uint8_t index) {
    switch(index) {
      case 0 + offset: return serial0.read();
      case 1 + offset: return serial1.read();
      default: return -1;
    }
  }
  void begin(const long br) {
    if (portMask & FirstOutputMask)   serial0.begin(br);
    if (portMask & SecondOutputMask)  serial1.begin(br);
  }
  void end() {
    if (portMask & FirstOutputMask)   serial0.end();
    if (portMask & SecondOutputMask)  serial1.end();
  }
  bool connected() {
    bool ret = true;
    if (portMask & FirstOutputMask)   ret = CALL_IF_EXISTS(bool, &serial0, connected);
    if (portMask & SecondOutputMask)  ret = ret && CALL_IF_EXISTS(bool, &serial1, connected);
    return ret;
  }

  using BaseClassT::available;
  using BaseClassT::read;

  // Redirect flush
  NO_INLINE void flush()      {
    if (portMask & FirstOutputMask)   serial0.flush();
    if (portMask & SecondOutputMask)  serial1.flush();
  }
  NO_INLINE void flushTX()    {
    if (portMask & FirstOutputMask)   CALL_IF_EXISTS(void, &serial0, flushTX);
    if (portMask & SecondOutputMask)  CALL_IF_EXISTS(void, &serial1, flushTX);
  }

  MultiSerial(Serial0T & serial0, Serial1T & serial1, int8_t mask = AllMask, const bool e = false) :
    BaseClassT(e),
    portMask(mask), serial0(serial0), serial1(serial1) {}
};

// Build the actual serial object depending on current configuration
#define Serial0Type TERN(SERIAL_RUNTIME_HOOK, RuntimeSerial, BaseSerial)
#define ForwardSerial0Type TERN(SERIAL_RUNTIME_HOOK, RuntimeSerial, ForwardSerial)
#ifdef HAS_MULTI_SERIAL
  #define Serial1Type ConditionalSerial
#endif