1 module puppeteer.puppet_link.puppet_link; 2 3 import std.file; 4 import std.exception; 5 import std.format; 6 import std.conv; 7 import std.stdio; 8 import std.typecons; 9 import std.meta; 10 11 import core.thread; 12 13 import puppeteer.serial.i_serial_port; 14 import puppeteer.serial.ox_serial_port_wrapper; 15 16 import puppeteer.var_monitor_utils; 17 18 import puppeteer.puppet_link.is_puppet_link; 19 20 public class PuppetLink(IVTypes...) 21 { 22 static assert(isPuppetLink!(typeof(this), IVTypes)); 23 24 private ISerialPort serialPort; 25 26 private OnAIUpdateCallback onAIUpdateCallback; 27 28 @property 29 AIMonitorCallback(OnAIUpdateCallback callback) 30 { 31 onAIUpdateCallback = callback; 32 } 33 34 private Tuple!(staticMap!(OnIVUpdateCallback, IVTypes)) onIVUpdateCallbacks; 35 private alias onIVUpdateCallback(IVType) = Alias!(onIVUpdateCallbacks[staticIndexOf!(IVType, IVTypes)]); 36 37 @property 38 IVMonitorCallback(IVType)(OnIVUpdateCallback!IVType callback) 39 { 40 onIVUpdateCallbacks[staticIndexOf!(IVType, IVTypes)] = callback; 41 } 42 43 private enum ubyte commandControlByte = 0xff; 44 45 @property 46 bool isCommunicationOpen() 47 { 48 return serialPort.isOpen; 49 } 50 51 this(string devFileName) 52 in 53 { 54 assert(devFileName !is null); 55 } 56 body 57 { 58 enforce(exists(devFileName)); 59 enforce(isFile(devFileName)); 60 61 enum portReadTimeoutMs = 200; 62 serialPort = new OxSerialPortWrapper(devFileName, Parity.none, BaudRate.B9600, portReadTimeoutMs); 63 } 64 65 bool startCommunication() 66 { 67 if(serialPort.open()) 68 { 69 //Some puppets seems to need some time between port opening and communication start 70 Thread.sleep(dur!"seconds"(1)); 71 72 enum ubyte[] puppeteerReadyCommand = [0x0, 0x0]; 73 serialPort.write([commandControlByte] ~ puppeteerReadyCommand); 74 75 enum ubyte[] puppetReadyCommand = [0x0, 0x0]; 76 ubyte[] readCache = []; 77 78 enum msBetweenChecks = 100; 79 int readCounter = 0; 80 enum readsUntilFailure = 30; 81 82 while(readCounter++ < readsUntilFailure) 83 { 84 ubyte[] readBytes = serialPort.read(1); 85 86 if(readBytes !is null) 87 { 88 readCache ~= readBytes; 89 debug writeln("handshake cache is currently ", readCache); 90 91 if(readCache.length == 3) 92 { 93 if(readCache == [commandControlByte] ~ puppetReadyCommand) 94 return true; //Ready! 95 else 96 readCache = readCache[1..$]; //pop front and continue 97 } 98 } 99 100 Thread.sleep(dur!"msecs"(msBetweenChecks)); 101 } 102 } 103 104 return false; 105 } 106 107 void endCommunication() 108 { 109 serialPort.close(); 110 } 111 112 void setAIMonitor(ubyte AIPin, bool monitor) 113 { 114 ubyte opCode = monitor ? 0x01 : 0x02; 115 serialPort.write([commandControlByte, opCode, AIPin]); 116 } 117 118 void setIVMonitor(VarMonitorTypeCode IVTypeCode, ubyte IVIndex, bool monitor) 119 { 120 ubyte opCode = monitor ? 0x05 : 0x06; 121 serialPort.write([commandControlByte, opCode, IVTypeCode, IVIndex]); 122 } 123 124 void setPWMOut(ubyte PWMOutPin, ubyte value) 125 { 126 ubyte opCode = 0x04; 127 serialPort.write([commandControlByte, opCode, PWMOutPin, value]); 128 } 129 130 void readPuppet(long communicationMsTime) 131 { 132 enum bytesReadAtOnce = 1; 133 134 ubyte[] readBytes = serialPort.read(bytesReadAtOnce); 135 136 if(readBytes !is null) 137 { 138 debug(3) writeln("Read bytes ", readBytes); 139 handleReadBytes(readBytes, communicationMsTime); 140 } 141 } 142 143 private void handleReadBytes(ubyte[] readBytes, long communicationTimeMillis) 144 in 145 { 146 assert(readBytes !is null); 147 } 148 body 149 { 150 static ubyte[] readBuffer; 151 152 void popReadBuffer(size_t elements) 153 { 154 readBuffer = readBuffer[elements .. $]; 155 } 156 157 if(readBytes.length > 0) 158 { 159 readBuffer ~= readBytes; 160 161 if(readBuffer[0] != commandControlByte) 162 { 163 debug writeln("Received corrupt command. Discarding first byte and returning"); 164 popReadBuffer(1); 165 } 166 else if (readBuffer.length >= 2) 167 { 168 with (ReadCommands) switch(readBuffer[1]) 169 { 170 case analogMonitor: 171 if(readBuffer.length < 5) 172 return; 173 174 handleAIMonitorCommand(readBuffer[1 .. 5], communicationTimeMillis); 175 popReadBuffer(5); 176 break; 177 178 case varMonitor: 179 if(readBuffer.length < 6) 180 return; 181 182 handleVarMonitorCommand(readBuffer[1..6], communicationTimeMillis); 183 popReadBuffer(6); 184 break; 185 186 case error: 187 //TODO 188 break; 189 190 default: 191 writeln("Unhandled ubyte command received: ", readBuffer[0], ". Cleaning command buffer."); 192 readBuffer = []; 193 } 194 } 195 } 196 } 197 198 private void handleAIMonitorCommand(ubyte[] cmd, long communicationMsTime) 199 in 200 { 201 assert(cmd !is null); 202 assert(cmd.length == 4); 203 assert(cmd[0] == ReadCommands.analogMonitor); 204 } 205 body 206 { 207 debug(2) writeln("Handling analogMonitorCommand ", command); 208 209 if(onAIUpdateCallback is AIMonitorListenerT.init) 210 return; 211 212 ubyte pin = cmd[1]; 213 214 enum analogReadMax = 1023; 215 enum analogReference = 5; 216 enum possibleValues = 256; 217 218 ushort encodedValue = cmd[2] * possibleValues + cmd[3]; 219 float readValue = analogReference * to!float(encodedValue) / analogReadMax; 220 221 onAIUpdateCallback.onAIUpdate(pin, readValue, communicationMsTime); 222 } 223 224 private void handleVarMonitorCommand(ubyte[] cmd, long communicationMsTime) 225 in 226 { 227 assert(cmd !is null); 228 assert(cmd.length == 5); 229 assert(cmd[0] == ReadCommands.varMonitor); 230 } 231 body 232 { 233 debug(2) writeln("Handling varMonitorCommand ", cmd); 234 235 void delegate (ubyte, ubyte[], long) selectDelegate(VarMonitorTypeCode typeCode) 236 { 237 string generateSwitch() 238 { 239 string str = "switch (typeCode) with (VarMonitorTypeCode) {"; 240 241 foreach(IVType; IVTypes) 242 str ~= format("case %s: return &handleData!%s;", 243 to!string(varMonitorTypeCode!IVType), 244 IVType.stringof); 245 246 str ~= "default: return null; }" ; 247 248 return str; 249 } 250 251 mixin(generateSwitch()); 252 } 253 254 try 255 { 256 auto handler = selectDelegate(to!VarMonitorTypeCode(cmd[1])); 257 if(handler) 258 handler(cmd[2], cmd[3..$], communicationMsTime); 259 else 260 { 261 writefln("Received unhandled VarMonitorTypeCode %s", cmd[1]); 262 } 263 } 264 catch(ConvException e) 265 { 266 writeln("Received invalid varMonitor type: ",e); 267 } 268 } 269 270 private void handleData(IVType)(ubyte varIndex, ubyte[] data, long communicationMsTime) 271 { 272 IVType decodeData(IVType : short)(ubyte[] data) pure 273 { 274 enum ubytePossibleValues = 256; 275 return to!short(data[0] * ubytePossibleValues + data[1]); 276 } 277 278 auto decodedData = decodeData!IVType(data); 279 280 _IVMonitorListener.onIVUpdate(varIndex, decodedData, communicationMsTime); 281 } 282 283 private enum ReadCommands : ubyte 284 { 285 analogMonitor = 0x1, 286 varMonitor = 0x2, 287 error = 0xfe 288 } 289 }