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);
        }
Esempio n. 2
0
        /// <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);
        }
Esempio n. 3
0
 /// <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);
 }
Esempio n. 4
0
        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;
            }
        }