pascalabcnet/bin/Lib/BBCMicrobit.pas

190 lines
7 KiB
ObjectPascal
Raw Normal View History

2019-08-22 22:36:08 +03:00
// 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.