using System; using System.Collections.Generic; using System.IO; using System.IO.Ports; using System.Linq; using UnityEngine; using UnityEngine.UIElements; // Handle the serial communication with the Arduino, // the cartridge transfer and related UI interactions. public class Arduino_Com : MonoBehaviour { private enum ArduinoState { Déconnecté, Connecté, Connexion, Lecture, Écriture, Invalide } // ======== UI ======== [SerializeField] private UIDocument ui; private DropdownField _arduinoSerialPort; private EnumField _arduinoState; private Button _arduinoControlButton; private ProgressBar _arduinoProgress; private Button _cartLoadButton; private Button _cartWriteButton; private VisualElement _cartImage; // ======== IO ======== private SerialPort _serial = new (); // Probably should be left alone but give the possibility to change it in the inspector. [SerializeField] private int baudrate = 115200; private string _activeSerialPort; private ArduinoState _state = ArduinoState.Déconnecté; private string _cartPath; private FileStream _cart; // ======== Cart transfer ======== // Page data corresponding to the EEPROM. private const int PageSize = 128; private const int PageCount = 512; // Page to be written out. private Byte[] _pageBuffer = new byte[PageSize]; // Current position in _pageBuffer. private int _readBytes; private int _currentPage; // Checksum a 128 bytes page of data by summing it into an unsigned 16 bits int, // to be used either as a verification with a received checksum or to negate and // send as the checksum. private static UInt16 ComputeChecksum(Byte[] data) { if (data.Length != PageSize) { throw new InvalidDataException("Checksum only accepts "+PageSize+" bytes arrays !"); } UInt16 sum = 0; for (var i = 0; i < PageSize; i += 2) { sum += BitConverter.ToUInt16(data, i); } return sum; } // Core communication function. // Receives all the data from the cartridge and writes it out to the png file. // As the computer will probably read the serial data faster than the Arduino // /and/ the transfer is quite slow (lots of bits, low baudrate), make this a // coroutine. // This prevents the whole application freezing up why waiting for data and // allow presenting visual feedback. private IEnumerator ReceiveSerialData() { do { // We could use an event instead but as Unity is lagging behind .NET versions, // it wouldn't be cross-platform. yield return new WaitUntil(() => _serial.BytesToRead >= 1); // Read all available data, up to the PageSize. _readBytes += _serial.Read(_pageBuffer, _readBytes, PageSize - _readBytes); // Full page received, check checksum and ACK/NAK, write received page. if (_readBytes >= PageSize) { Byte[] checksum = new byte[2]; // There might be less than the two characters of the checksum available, loop until complete. var checksumRead = 0; do { checksumRead += _serial.Read(checksum, checksumRead, 2-checksumRead); } while (checksumRead < 2); // Implicit type of the sum is 4 bytes, so the overflow doesn't occur. Compute manually. if ((UInt16.MaxValue + 1) - (BitConverter.ToUInt16(checksum) + ComputeChecksum(_pageBuffer)) == 0) { // Checksum valid, write it out to the cart. _cart.Write(_pageBuffer); _currentPage += 1; _serial.Write("ACK\n"); } else { // Checksum invalid, ask for retransmit. _serial.Write("NAK\n"); Debug.LogWarningFormat("Page {0} reception failed, asking for retransmit.", _currentPage); } _readBytes = 0; } } while (_currentPage < PageCount); // We are done receiving the cart, we don't expect more data to be received so the coroutine can return. _currentPage = 0; _state = ArduinoState.Connecté; _cart.Close(); // Re-enable buttons now that the transfer is done. _arduinoControlButton.SetEnabled(true); _cartLoadButton.SetEnabled(true); // _cartWriteButton.SetEnabled(true); TODO: Not implemented _arduinoProgress.visible = false; // Update cart image ! var newCartTexture = new Texture2D(2, 2); var textureData = File.ReadAllBytes(_cartPath); newCartTexture.LoadImage(textureData); _cartImage.style.backgroundImage = new StyleBackground(newCartTexture); } void Start() { // ======== UI ======== var uiRoot = ui.rootVisualElement; _arduinoSerialPort = uiRoot.Query("Arduino-Serial"); _arduinoSerialPort.RegisterValueChangedCallback(OnNewSerialPort); _arduinoSerialPort.choices = new List(SerialPort.GetPortNames()); _arduinoState = uiRoot.Query("Arduino-State"); _arduinoControlButton = uiRoot.Query