protected override PinMode GetPinMode(int pinNumber) { if (_pinModes.TryGetValue(pinNumber, out var existingValue)) { return(existingValue); } SupportedMode mode = _arduinoBoard.Firmata.GetPinMode(pinNumber); PinMode ret; switch (mode) { case SupportedMode.DigitalOutput: ret = PinMode.Output; break; case SupportedMode.InputPullup: ret = PinMode.InputPullUp; break; case SupportedMode.DigitalInput: ret = PinMode.Input; break; default: _arduinoBoard.Log($"Unexpected pin mode found: {mode}. Is the pin not set to GPIO?"); ret = PinMode.Input; break; } _pinModes[pinNumber] = ret; return(ret); }
/// <summary> /// Returns the current assignment of the given pin /// </summary> /// <param name="pinNumber">Pin number to query</param> /// <returns>A value of the <see cref="PinUsage"/> enumeration</returns> public override PinUsage DetermineCurrentPinUsage(int pinNumber) { SupportedMode mode = Firmata.GetPinMode(pinNumber); switch (mode) { case SupportedMode.AnalogInput: return(PinUsage.AnalogIn); case SupportedMode.DigitalInput: return(PinUsage.Gpio); case SupportedMode.DigitalOutput: return(PinUsage.Gpio); case SupportedMode.Pwm: return(PinUsage.Pwm); case SupportedMode.Servo: break; case SupportedMode.Shift: break; case SupportedMode.I2c: return(PinUsage.I2c); case SupportedMode.OneWire: break; case SupportedMode.Stepper: break; case SupportedMode.Encoder: break; case SupportedMode.Serial: return(PinUsage.Uart); case SupportedMode.InputPullup: return(PinUsage.Gpio); case SupportedMode.Spi: return(PinUsage.Spi); case SupportedMode.Sonar: break; case SupportedMode.Tone: break; case SupportedMode.Dht: return(PinUsage.Gpio); } return(PinUsage.Unknown); }
/// <summary> /// Sets the internal pin mode to the given value, if supported. /// </summary> /// <param name="pin">The pin to configure</param> /// <param name="arduinoMode">The mode to set</param> /// <exception cref="TimeoutException">The mode was not updated, either because the command was not understood or /// the mode is unknown by the firmware</exception> /// <remarks>This method is intended for use by <see cref="ExtendedCommandHandler"/> instances. Users should not /// call this method directly. It is the responsibility of the command handler to use the capabilities table to check /// that the mode is actually supported</remarks> public void SetPinMode(int pin, SupportedMode arduinoMode) { Firmata.SetPinMode(pin, arduinoMode); }
private void ProcessInput() { if (_dataQueue.Count == 0) { FillQueue(); } if (_dataQueue.Count == 0) { // Still no data? (End of stream or stream closed) return; } int data = _dataQueue.Dequeue(); // OnError?.Invoke($"0x{data:X}", null); byte b = (byte)(data & 0x00FF); byte upper_nibble = (byte)(data & 0xF0); byte lower_nibble = (byte)(data & 0x0F); /* * the relevant bits in the command depends on the value of the data byte. If it is less than 0xF0 (start sysex), only the upper nibble identifies the command * while the lower nibble contains additional data */ FirmataCommand command = (FirmataCommand)((data < ((ushort)FirmataCommand.START_SYSEX) ? upper_nibble : b)); // determine the number of bytes remaining in the message int bytes_remaining = 0; bool isMessageSysex = false; switch (command) { default: // command not understood char c = (char)data; _lastRawLine.Append(c); if (c == '\n') { OnError?.Invoke(_lastRawLine.ToString().Trim(), null); OnSysexReply?.Invoke(ReplyType.AsciiData, Encoding.Unicode.GetBytes(_lastRawLine.ToString())); _lastRawLine.Clear(); } return; case FirmataCommand.END_SYSEX: // should never happen return; // commands that require 2 additional bytes case FirmataCommand.DIGITAL_MESSAGE: case FirmataCommand.ANALOG_MESSAGE: case FirmataCommand.SET_PIN_MODE: case FirmataCommand.PROTOCOL_VERSION: bytes_remaining = 2; break; // commands that require 1 additional byte case FirmataCommand.REPORT_ANALOG_PIN: case FirmataCommand.REPORT_DIGITAL_PIN: bytes_remaining = 1; break; // commands that do not require additional bytes case FirmataCommand.SYSTEM_RESET: // do nothing, as there is nothing to reset return; case FirmataCommand.START_SYSEX: // this is a special case with no set number of bytes remaining isMessageSysex = true; _lastRawLine.Clear(); break; } // read the remaining message while keeping track of elapsed time to timeout in case of incomplete message List <byte> message = new List <byte>(); int bytes_read = 0; Stopwatch timeout_start = Stopwatch.StartNew(); while (bytes_remaining > 0 || isMessageSysex) { if (_dataQueue.Count == 0) { int timeout = 10; while (!FillQueue() && timeout-- > 0) { Thread.Sleep(5); } if (timeout == 0) { // Synchronisation problem: The remainder of the expected message is missing _lastCommandError = CommandError.Timeout; return; } } data = _dataQueue.Dequeue(); // OnError?.Invoke($"0x{data:X}", null); // if no data was available, check for timeout if (data == 0xFFFF) { // get elapsed seconds, given as a double with resolution in nanoseconds var elapsed = timeout_start.Elapsed; if (elapsed > DefaultReplyTimeout) { _lastCommandError = CommandError.Timeout; return; } continue; } timeout_start.Restart(); // if we're parsing sysex and we've just read the END_SYSEX command, we're done. if (isMessageSysex && (data == (short)FirmataCommand.END_SYSEX)) { break; } message.Add((byte)(data & 0xFF)); ++bytes_read; --bytes_remaining; } // process the message switch (command) { // ignore these message types (they should not be in a reply) default: case FirmataCommand.REPORT_ANALOG_PIN: case FirmataCommand.REPORT_DIGITAL_PIN: case FirmataCommand.SET_PIN_MODE: case FirmataCommand.END_SYSEX: case FirmataCommand.SYSTEM_RESET: return; case FirmataCommand.PROTOCOL_VERSION: if (_actualFirmataProtocolMajorVersion != 0) { // Firmata sends this message automatically after a device reset (if you press the reset button on the arduino) // If we know the version already, this is unexpected. _lastCommandError = CommandError.DeviceReset; OnError?.Invoke("The device was unexpectedly reset. Please restart the communication.", null); } _actualFirmataProtocolMajorVersion = message[0]; _actualFirmataProtocolMinorVersion = message[1]; _dataReceived.Set(); return; case FirmataCommand.ANALOG_MESSAGE: // report analog commands store the pin number in the lower nibble of the command byte, the value is split over two 7-bit bytes // AnalogValueUpdated(this, // new CallbackEventArgs(lower_nibble, (ushort)(message[0] | (message[1] << 7)))); { int pin = lower_nibble; uint value = (uint)(message[0] | (message[1] << 7)); lock (_lastAnalogValueLock) { _lastAnalogValues[pin] = value; } AnalogPinValueUpdated?.Invoke(pin, value); } break; case FirmataCommand.DIGITAL_MESSAGE: // digital messages store the port number in the lower nibble of the command byte, the port value is split over two 7-bit bytes // Each port corresponds to 8 pins { int offset = lower_nibble * 8; ushort pinValues = (ushort)(message[0] | (message[1] << 7)); lock (_lastPinValueLock) { for (int i = 0; i < 8; i++) { PinValue oldValue = _lastPinValues[i + offset]; int mask = 1 << i; PinValue newValue = (pinValues & mask) == 0 ? PinValue.Low : PinValue.High; if (newValue != oldValue) { PinEventTypes eventTypes = newValue == PinValue.High ? PinEventTypes.Rising : PinEventTypes.Falling; _lastPinValues[i + offset] = newValue; // TODO: The callback should not be within the lock DigitalPortValueUpdated?.Invoke(this, new PinValueChangedEventArgs(eventTypes, i + offset)); } } } } break; case FirmataCommand.START_SYSEX: // a sysex message must include at least one extended-command byte if (bytes_read < 1) { _lastCommandError = CommandError.InvalidArguments; return; } // retrieve the raw data array & extract the extended-command byte var raw_data = message.ToArray(); FirmataSysexCommand sysCommand = (FirmataSysexCommand)(raw_data[0]); int index = 0; ++index; --bytes_read; switch (sysCommand) { case FirmataSysexCommand.REPORT_FIRMWARE: // See https://github.com/firmata/protocol/blob/master/protocol.md // Byte 0 is the command (0x79) and can be skipped here, as we've already interpreted it { _firmwareVersionMajor = raw_data[1]; _firmwareVersionMinor = raw_data[2]; int stringLength = (raw_data.Length - 3) / 2; Span <byte> bytesReceived = stackalloc byte[stringLength]; ReassembleByteString(raw_data, 3, stringLength * 2, bytesReceived); _firmwareName = Encoding.ASCII.GetString(bytesReceived); _dataReceived.Set(); } return; case FirmataSysexCommand.STRING_DATA: { // condense back into 1-byte data int stringLength = (raw_data.Length - 1) / 2; Span <byte> bytesReceived = stackalloc byte[stringLength]; ReassembleByteString(raw_data, 1, stringLength * 2, bytesReceived); string message1 = Encoding.UTF8.GetString(bytesReceived); int idxNull = message1.IndexOf('\0'); if (message1.Contains("%") && idxNull > 0) // C style printf formatters { message1 = message1.Substring(0, idxNull); string message2 = PrintfFromByteStream(message1, bytesReceived, idxNull + 1); OnError?.Invoke(message2, null); } else { OnError?.Invoke(message1, null); } } break; case FirmataSysexCommand.CAPABILITY_RESPONSE: { _supportedPinConfigurations.Clear(); int idx = 1; var currentPin = new SupportedPinConfiguration(0); int pin = 0; while (idx < raw_data.Length) { int mode = raw_data[idx++]; if (mode == 0x7F) { _supportedPinConfigurations.Add(currentPin); currentPin = new SupportedPinConfiguration(++pin); continue; } int resolution = raw_data[idx++]; SupportedMode?sm = SupportedModes.FirstOrDefault(x => x.Value == mode); if (sm == SupportedMode.AnalogInput) { currentPin.PinModes.Add(SupportedMode.AnalogInput); currentPin.AnalogInputResolutionBits = resolution; } else if (sm == SupportedMode.Pwm) { currentPin.PinModes.Add(SupportedMode.Pwm); currentPin.PwmResolutionBits = resolution; } else if (sm == null) { sm = new SupportedMode((byte)mode, $"Unknown mode {mode}"); currentPin.PinModes.Add(sm); } else { currentPin.PinModes.Add(sm); } } // Add 8 entries, so that later we do not need to check whether a port (bank) is complete _lastPinValues = new PinValue[_supportedPinConfigurations.Count + 8].ToList(); _dataReceived.Set(); // Do not add the last instance, should also be terminated by 0xF7 } break; case FirmataSysexCommand.ANALOG_MAPPING_RESPONSE: { // This needs to have been set up previously if (_supportedPinConfigurations.Count == 0) { return; } int idx = 1; int pin = 0; while (idx < raw_data.Length) { if (raw_data[idx] != 127) { _supportedPinConfigurations[pin].AnalogPinNumber = raw_data[idx]; } idx++; pin++; } _dataReceived.Set(); } break; case FirmataSysexCommand.I2C_REPLY: _lastCommandError = CommandError.None; _lastResponse = raw_data; _dataReceived.Set(); break; case FirmataSysexCommand.SPI_DATA: _lastCommandError = CommandError.None; _lastResponse = raw_data; _dataReceived.Set(); break; default: // we pass the data forward as-is for any other type of sysex command _lastCommandError = CommandError.None; _lastResponse = raw_data; // the instance is constant, so we can just remember the pointer OnSysexReply?.Invoke(ReplyType.SysexCommand, raw_data); _dataReceived.Set(); break; } break; } }