190 lines
7 KiB
ObjectPascal
190 lines
7 KiB
ObjectPascal
// BBC Micro:bit communication module
|
||
|
||
unit BBCMicrobit;
|
||
|
||
interface
|
||
|
||
/// Исключение для информирования об отсутствии подключенного устройства
|
||
type
|
||
NoUBSComPortsException = class(Exception) end;
|
||
NoBBCBoardFoundException = class(Exception) end;
|
||
|
||
/// Класс для взаимодействия с устройством BBC Micro:bit
|
||
type
|
||
Microbit = class(System.IDisposable)
|
||
|
||
protected
|
||
/// Порт для связи с устройством
|
||
Port: System.IO.Ports.SerialPort;
|
||
|
||
/// Обработчик данных от платы. Принимает только строки
|
||
procedure DataReceivedHandler(sender: object; e: System.IO.Ports.SerialDataReceivedEventArgs);
|
||
|
||
|
||
|
||
public
|
||
|
||
/// Копирование файла прошивки в BBC:Microbit
|
||
class procedure CopyFile(filename : string);
|
||
|
||
public
|
||
/// Обработчик события распознавания
|
||
OnReadString: procedure(Message: string);
|
||
|
||
/// Список USB COM-портов с идентификаторами Microbit
|
||
class function GetUsbPortNames: List<string>;
|
||
begin
|
||
// Вот такие параметры у каждой платы (по крайней мере сейчас)
|
||
var pattern := '^VID_0D28.PID_0204';
|
||
var _rx := new System.Text.RegularExpressions.Regex(pattern, RegexOptions.IgnoreCase);
|
||
Result := new List<string>;
|
||
var {Microsoft.Win32.RegistryKey} rk1 := Microsoft.Win32.Registry.LocalMachine;
|
||
var {Microsoft.Win32.RegistryKey} rk2 := rk1.OpenSubKey('SYSTEM\\CurrentControlSet\\Enum');
|
||
|
||
foreach s3: string in rk2.GetSubKeyNames do
|
||
begin
|
||
var rk3 := rk2.OpenSubKey(s3);
|
||
foreach s: string in rk3.GetSubKeyNames do
|
||
if _rx.Match(s).Success then
|
||
begin
|
||
var {RegistryKey} rk4 := rk3.OpenSubKey(s);
|
||
foreach s2: string in rk4.GetSubKeyNames do
|
||
begin
|
||
var {RegistryKey} rk5 := rk4.OpenSubKey(s2);
|
||
var {RegistryKey} rk6 := rk5.OpenSubKey('Device Parameters');
|
||
var {string} portName := string(rk6.GetValue('PortName'));
|
||
if not String.IsNullOrEmpty(portName) and System.IO.Ports.SerialPort.GetPortNames.Contains(portName) then
|
||
begin
|
||
Result.Add(string(rk6.GetValue('PortName')));
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
/// <summary>
|
||
/// Конструктор объекта для взаимодействия с Microbit
|
||
/// </summary>
|
||
/// <param name="Handler">Процедура-обработчик, вызываемая для каждой принятой строки</param>
|
||
/// <param name="PortName">Имя COM-порта, можно не указывать</param>
|
||
constructor Create(Handler: procedure(s: string) := nil; PortName: string := '');
|
||
begin
|
||
OnReadString := Handler;
|
||
// Инициализируем виртуальный COM-порт, который связан с USB-устройством.
|
||
Port := new System.IO.Ports.SerialPort;
|
||
|
||
if PortName.Length > 0 then
|
||
// Порт указан явно при создании объекта класса, значит его и используем
|
||
Port.PortName := PortName
|
||
else
|
||
begin
|
||
// Порт явно не указан, приходится искать самостоятельно
|
||
// Получаем список всех похожих COM-портов (по VID и PID)
|
||
var portNames := GetUsbPortNames;
|
||
// Если подходящих портов нет - выбрасываем исключение
|
||
if portNames.Count = 0 then
|
||
raise new NoUBSComPortsException('Не найдено устройство Microbit')
|
||
else
|
||
// Берём первый попавшийся. Если устройств несколько, то надо явно указывать
|
||
Port.PortName := portNames[0];
|
||
end;
|
||
|
||
// Эти параметры зашиты явно, рекомендованы руководством по Microbit
|
||
Port.BaudRate := 115200; //Указываем скорость.
|
||
Port.DataBits := 8;
|
||
Port.Parity := System.IO.Ports.Parity.None;
|
||
Port.StopBits := System.IO.Ports.StopBits.One;
|
||
Port.DataReceived += DataReceivedHandler;
|
||
Port.Open; //Открываем порт
|
||
end;
|
||
|
||
/// Отправка строки на устройство Microbit
|
||
procedure Send(message: string) := Port.WriteLine(message);
|
||
|
||
/// Деструктор - закрывает COM-порт
|
||
destructor Destroy;
|
||
begin
|
||
if Port <> nil then
|
||
begin
|
||
Port.Close;
|
||
Port := nil;
|
||
end;
|
||
end;
|
||
|
||
procedure Dispose;
|
||
begin
|
||
Destroy;
|
||
end;
|
||
|
||
procedure Finalize; override;
|
||
begin
|
||
Destroy
|
||
end;
|
||
end;
|
||
|
||
implementation
|
||
|
||
/// Иногда строки приходят "склеенные" – нужно разбить по служебным символам
|
||
/// Эта функция возвращает первую группу символов из строки, ограниченную
|
||
/// служебными #10 или #13, сама подстрока из строки удаляется.
|
||
function ParseStr(var s : string) : string;
|
||
begin
|
||
var iStart := 1;
|
||
var len := s.Length;
|
||
|
||
while (iStart<=len) and ((s[iStart] = #10) or (s[iStart] = #13)) do
|
||
iStart += 1;
|
||
|
||
var iEnd := iStart + 1;
|
||
|
||
while (iEnd<=len) and (s[iEnd] <> #10) and (s[iEnd] <> #13) do
|
||
iEnd += 1;
|
||
|
||
// Копируем фрагмент, но с проверкой
|
||
if iEnd-iStart > 0 then
|
||
Result := copy(s, iStart, iEnd-iStart)
|
||
else
|
||
Result := '';
|
||
|
||
while (iEnd<len) and ((s[iEnd+1] = #10) or (s[iEnd+1] = #13)) do
|
||
iEnd += 1;
|
||
Delete(s,1,iEnd);
|
||
end;
|
||
|
||
procedure Microbit.DataReceivedHandler(sender: object; e: System.IO.Ports.SerialDataReceivedEventArgs);
|
||
begin
|
||
var sp := sender as System.IO.Ports.SerialPort;
|
||
|
||
// Читаем строку по ReadLine
|
||
var message := sp.ReadLine;
|
||
|
||
|
||
while message.Length > 0 do
|
||
begin
|
||
// Очередная подстрока
|
||
var block := ParseStr(message).TrimEnd(' ');
|
||
if (block.Length > 0) and (OnReadString <> nil) then
|
||
OnReadString(block);
|
||
end;
|
||
end;
|
||
|
||
class procedure MicroBit.CopyFile(filename : string);
|
||
begin
|
||
var allDrives := System.IO.DriveInfo.GetDrives();
|
||
|
||
foreach d: System.IO.DriveInfo in allDrives do
|
||
begin
|
||
if d.IsReady then
|
||
if d.VolumeLabel = 'MICROBIT' then
|
||
begin
|
||
var dest : string := d.RootDirectory.ToString + '\\' + System.IO.Path.GetFileName(filename);
|
||
System.IO.File.Copy(filename, dest);
|
||
sleep(2000);
|
||
exit;
|
||
end;
|
||
end;
|
||
raise new NoBBCBoardFoundException('Не найдено устройство Microbit');
|
||
|
||
end;
|
||
|
||
end. |