diff --git a/Assets/Scripts/Arduino_Com.cs b/Assets/Scripts/Arduino_Com.cs new file mode 100644 index 0000000..c5f1bad --- /dev/null +++ b/Assets/Scripts/Arduino_Com.cs @@ -0,0 +1,247 @@ +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é, + 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; + } + + private IEnumerator ReceiveSerialData() + { + do + { + 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 >= 128) + { + 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 (65536 - (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