1 module puppeteer.puppeteer;
2 
3 import test.puppeteer.puppeteer_test : test;
4 mixin test;
5 
6 public import puppeteer.serial.i_serial_port;
7 
8 import puppeteer.signal_wrapper;
9 import puppeteer.var_monitor_utils;
10 
11 import puppeteer.logging.i_puppeteer_logger;
12 
13 import puppeteer.value_adapter.invalid_adapter_expression_exception;
14 
15 import puppeteer.communication.is_communicator;
16 
17 import puppeteer.configuration.i_configuration;
18 
19 import std.stdio;
20 import std.concurrency;
21 import std.conv;
22 import std.typecons;
23 import std.exception;
24 import std.signals;
25 import std.meta;
26 import std.datetime;
27 import std.exception;
28 
29 import core.time;
30 
31 public alias pinListenerDelegate = void delegate (ubyte, float, float, long) shared;
32 private alias varMonitorDelegateType(VarType) = AliasSeq!(ubyte, VarType, VarType, long);
33 
34 // Currently disabled due to not being able to be expanded into a function argument and then used from another type
35 // Manually writing the delegate type as the function argument will have to do by now
36 @disable
37 public alias varMonitorDelegate(VarType) = void delegate (varMonitorDelegateType!VarType) shared;
38 
39 shared class Puppeteer(CommunicatorT, VarMonitorTypes...)
40 if(isCommunicator!(CommunicatorT, VarMonitorTypes) && allSatisfy!(isVarMonitorTypeSupported, VarMonitorTypes))
41 {
42     alias PinSignalWrapper = SignalWrapper!(ubyte, float, float, long);
43 
44     //Manually synchronized between both logical threads
45     protected PinSignalWrapper[ubyte] pinSignalWrappers;
46     protected mixin(unrollVariableSignalWrappers!VarMonitorTypes());
47 
48     protected CommunicatorT communicator;
49 
50     protected IConfiguration!VarMonitorTypes config;
51 
52     protected IPuppeteerLogger logger;
53 
54     @property
55     shared(IConfiguration!VarMonitorTypes) configuration()
56     {
57         return config;
58     }
59 
60     enum canMonitor(T) = __traits(compiles, mixin(getVarMonitorSignalWrappersName!T));
61 
62     @property
63     bool isCommunicationEstablished()
64     {
65         return communicator.isCommunicationOngoing;
66     }
67 
68     this(scope shared CommunicatorT communicator, shared IConfiguration!VarMonitorTypes configuration, scope shared IPuppeteerLogger logger)
69     {
70         this.communicator = communicator;
71         this.config = configuration;
72         this.logger = logger;
73     }
74 
75     ~this()
76     {
77         destroy(logger);
78     }
79 
80     public void addPinListener(ubyte pin, pinListenerDelegate listener)
81     in
82     {
83         assert(listener !is null);
84     }
85     body
86     {
87         auto wrapper = pin in pinSignalWrappers;
88         if(!wrapper)
89         {
90             shared PinSignalWrapper signalWrapper = new PinSignalWrapper;
91 
92             pinSignalWrappers[pin] = signalWrapper;
93 
94             //No need to synchronize this call since it is the first listener
95             signalWrapper.addListener(listener);
96 
97             communicator.setAIMonitor(pin, true);
98         }
99         else
100         {
101             shared PinSignalWrapper signalWrapper = *wrapper;
102 
103             synchronized(signalWrapper)
104                 signalWrapper.addListener(listener);
105         }
106     }
107 
108     public void removePinListener(ubyte pin, pinListenerDelegate listener)
109     in
110     {
111         assert(listener !is null);
112     }
113     body
114     {
115         enforce(pin in pinSignalWrappers);
116 
117         shared PinSignalWrapper signalWrapper = pinSignalWrappers[pin];
118 
119         synchronized(signalWrapper)
120             signalWrapper.removeListener(listener);
121 
122         if(signalWrapper.listenersNumber == 0)
123         {
124             pinSignalWrappers.remove(pin);
125             communicator.setAIMonitor(pin, false);
126         }
127     }
128 
129     public void addVariableListener(VarType)(ubyte varIndex, void delegate(ubyte, VarType, VarType, long) shared listener)
130     if(canMonitor!VarType)
131     in
132     {
133         assert(listener !is null);
134     }
135     body
136     {
137         alias typeSignalWrappers = Alias!(mixin(getVarMonitorSignalWrappersName!VarType));
138         auto wrapper = varIndex in typeSignalWrappers;
139 
140         if(wrapper)
141         {
142             synchronized(*wrapper)
143                 wrapper.addListener(listener);
144         }
145         else
146         {
147             auto signalWrapper = new shared SignalWrapper!(varMonitorDelegateType!VarType);
148             signalWrapper.addListener(listener);
149             typeSignalWrappers[varIndex] = signalWrapper;
150 
151             communicator.setIVMonitor!VarType(varIndex, true);
152         }
153     }
154 
155     public void removeVariableListener(VarType)(ubyte varIndex, void delegate(ubyte, VarType, VarType, long) shared listener)
156     if(canMonitor!VarType)
157     in
158     {
159         assert(listener !is null);
160     }
161     body
162     {
163         alias typeSignalWrappers = Alias!(mixin(getVarMonitorSignalWrappersName!VarType));
164 
165         enforce(varIndex in typeSignalWrappers);
166 
167         auto signalWrapper = typeSignalWrappers[varIndex];
168 
169         synchronized(signalWrapper)
170             signalWrapper.removeListener(listener);
171 
172         if(signalWrapper.listenersNumber == 0)
173         {
174             typeSignalWrappers.remove(varIndex);
175             communicator.setIVMonitor!VarType(varIndex, false);
176         }
177     }
178 
179     public void setPWM(ubyte pin, ubyte value)
180     {
181         communicator.setPWMValue(pin, value);
182     }
183 
184     public void setPWMAverage(ubyte pin, float averageValue)
185     {
186         enum pwmHigh = 5;
187         float adaptedAvg = configuration.adaptPWMOutAvgValue(pin, averageValue);
188         setPWM(pin, getPWMFromAverage!pwmHigh(adaptedAvg));
189     }
190 
191     private ubyte getPWMFromAverage(float pwmHigh)(float averageValue)
192     {
193         import std.math;
194         return to!ubyte(round(averageValue / pwmHigh * ubyte.max));
195     }
196 
197     public bool startCommunication(string devFilename, BaudRate baudRate, Parity parity)
198     {
199         return communicator.startCommunication!()(devFilename, baudRate, parity);
200     }
201 
202     public void endCommunication()
203     {
204         communicator.endCommunication();
205 
206         //Remove all listeners
207         foreach(pin; pinSignalWrappers.byKey())
208             synchronized(pinSignalWrappers[pin])
209                 pinSignalWrappers.remove(pin);
210 
211 
212         //TODO: remove listeners for variables as well
213     }
214 
215     package void emitAIRead(byte pin, float readValue, long readMilliseconds)
216     {
217         auto signalWrapper = pin in pinSignalWrappers;
218         if(signalWrapper)
219         {
220             synchronized(*signalWrapper)
221                 signalWrapper.emit(pin, readValue, config.adaptAIValue(pin, readValue), readMilliseconds);
222         }
223         else
224         {
225             debug(2) writeln("No listeners registered for pin ",pin);
226         }
227 
228         logger.logSensor(readMilliseconds, config.getAISensorName(pin), to!string(readValue), to!string(config.adaptAIValue(pin, readValue)));
229     }
230 
231     package void emitVarRead(VarType)(byte varIndex, VarType readValue, long readMilliseconds)
232     if(canMonitor!VarType)
233     {
234         VarType adaptedValue = config.adaptVarMonitorValue(varIndex, readValue);
235 
236         alias varTypeSignalWrappers = Alias!(mixin(getVarMonitorSignalWrappersName!VarType));
237 
238         auto wrapper = varIndex in varTypeSignalWrappers;
239         if(wrapper)
240         {
241             synchronized(*wrapper)
242                 wrapper.emit(varIndex, readValue, adaptedValue, readMilliseconds);
243         }
244         else
245         {
246             debug(2) writeln("SignalWrapper for type ", VarType.stringof, "and varIndex ", varIndex, "was no longer in its SignalWrapper assoc array. Skipping signal emission.");
247         }
248 
249         logger.logSensor(readMilliseconds, config.getVarMonitorSensorName!VarType(varIndex), to!string(readValue), to!string(adaptedValue));
250     }
251 }
252 
253 private pure string unrollVariableSignalWrappers(VarTypes...)()
254 {
255     string unroll = "";
256 
257     foreach(varType; VarTypes)
258         unroll ~= getVarMonitorSignalWrappersType!varType ~ " " ~ getVarMonitorSignalWrappersName!varType ~ ";\n";
259 
260     return unroll;
261 }
262 
263 
264 private enum getVarMonitorSignalWrappersType(VarType) = "SignalWrapper!(ubyte, " ~ VarType.stringof ~ ", " ~ VarType.stringof ~ ", long)[ubyte]";
265 unittest
266 {
267     assert(getVarMonitorSignalWrappersType!int == "SignalWrapper!(ubyte, int, int, long)[ubyte]");
268     assert(getVarMonitorSignalWrappersType!float == "SignalWrapper!(ubyte, float, float, long)[ubyte]");
269     assert(getVarMonitorSignalWrappersType!byte == "SignalWrapper!(ubyte, byte, byte, long)[ubyte]");
270 }
271 
272 
273 private enum getVarMonitorSignalWrappersName(VarType) = VarType.stringof ~ "SignalWrappers";
274 unittest
275 {
276     assert(getVarMonitorSignalWrappersName!int == "intSignalWrappers");
277 }