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