Своя реализация монитора загрузки CPU и RAM из найденного в шухляде

от автора

Одним из самых любимых хабов на хабре всегда был для меня DIY, я и сам не прочь что-то сделать своими руками. Но так, как я в большей степени программист, а в меньшей — электронщик, сделанные мной «прототипы» всегда непрезентабельны. Данный девайс не исключение. Код тоже не причесан, т.к. это больше proof-of-concept чем коммерческое решение. Тем не менее, думаю данных пост будет полезен, и даже найдутся, те кто повторит эту поделку.

Вдохновившись постом о стрелочных Vu-метрах, показывающих загрузку CPU и использование RAM, решил сделать свой вариант. Не имея миниатюрных вольтметров, но имея покоривший своей ценой и простотой протокола 16х2 экран на базе контроллера hd44780 решил организовать визуализацию на нем. Как МК был выбран ланчпад MSP430G2, коих я купил жменьку, когда они были по $4.30. Ничего не мешает реализовать это все на любой arduino, нужно лишь поменять названия пинов.
Схема очень проста(взята из интернета):
image
Можно еще подключить подсветку и регулировку контраста, но думаю не составит труда разобраться с этим даже начинающим электронщикам(сужу по себе).
Программная часть состоит из двух частей, первая — скетч для Energia/Arduino. Все очень просто и понятно.

Скетч

#include <LiquidCrystal.h> byte cpuByte; byte ramByte; unsigned long lastUpdateTime; LiquidCrystal lcd(P2_3, P2_4, P1_5, P2_0, P2_1, P2_2); // У меня подключение не такое как в схеме выше  void setup() {   Serial.begin(9600);   lcd.begin(16, 2);   lcd.clear(); }  void loop() {      if (Serial.available() == 2) {       cpuByte = Serial.read();       ramByte = Serial.read();       lcd.setCursor(0, 0);       lcd.print("CPU             ");       lcd.setCursor(4, 0);       for (int i=0; i < cpuByte; i++) { lcd.write(255);}        lcd.setCursor(0, 1);       lcd.print("RAM             ");       lcd.setCursor(4, 1);       for (int i=0; i < ramByte; i++) { lcd.write(255);}              lastUpdateTime = millis();     }           if (millis()-lastUpdateTime > 3000) {       lcd.setCursor(0, 0);       lcd.print("  DISCONNECTED  ");       lcd.setCursor(0, 1);       lcd.print("                ");     }      delay(50);  } 

Логика проста, ждем два байта и выводим их значения в виде строки из символов с кодом 255. Прошлые значения затираю пробелами, ибо с lcd.clear() все мигает. Если более 3х секунд данные не приходят — DISCONNECTED.

Программирую я в основном под платформы 1Cх, а из компилируемых ЯП близок мне Delphi/Pascal больше всего. На Delphi версии XE2 и решил я писать вторую часть проекта. Так как планируется работа данного девайса на серверных ОС семейства Windows, ПО реализовано как служба.

Исходник на Delphi

unit Unit1;  interface  uses   Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.SvcMgr, Vcl.Dialogs, Registry;  type   TFPService = class(TService)     procedure ServiceExecute(Sender: TService);     procedure ServiceCreate(Sender: TObject);   private     { Private declarations }   public     function GetServiceController: TServiceController; override;     { Public declarations }   end;  var   FPService: TFPService;  const   SystemBasicInformation = 0;   SystemPerformanceInformation = 2;   SystemTimeInformation = 3;  type   TPDWord = ^DWORD;    TSystem_Basic_Information = packed record     dwUnknown1: DWORD;     uKeMaximumIncrement: ULONG;     uPageSize: ULONG;     uMmNumberOfPhysicalPages: ULONG;     uMmLowestPhysicalPage: ULONG;     uMmHighestPhysicalPage: ULONG;     uAllocationGranularity: ULONG;     pLowestUserAddress: Pointer;     pMmHighestUserAddress: Pointer;     uKeActiveProcessors: ULONG;     bKeNumberProcessors: byte;     bUnknown2: byte;     wUnknown3: word;   end; type   TSystem_Time_Information = packed record     liKeBootTime: LARGE_INTEGER;     liKeSystemTime: LARGE_INTEGER;     liExpTimeZoneBias: LARGE_INTEGER;     uCurrentTimeZoneId: ULONG;     dwReserved: DWORD;   end; type   TSystem_Performance_Information = packed record     liIdleTime: LARGE_INTEGER; {LARGE_INTEGER}     dwSpare: array[0..750] of DWORD;   end;  var   NtQuerySystemInformation: function(infoClass: DWORD; buffer: Pointer; bufSize: DWORD; returnSize: TPDword): DWORD; stdcall = nil;    SysBaseInfo: TSystem_Basic_Information;   SysPerfInfo: TSystem_Performance_Information;   SysTimeInfo: TSystem_Time_Information;    status: Longint; {long}   liOldIdleTime, liOldSystemTime: LARGE_INTEGER;   dbSystemTime, dbIdleTime, dbIdleTimePercent: Double;   hCom: THandle;    DCB:TDCB;   Errors, Bytes : Cardinal;   TheStruct:TCOMSTAT;   Timeouts: TCommTimeOuts;    ComNum:string;  implementation  {$R *.DFM}  procedure ServiceController(CtrlCode: DWord); stdcall; begin   FPService.Controller(CtrlCode); end;  function TFPService.GetServiceController: TServiceController; begin   Result := ServiceController; end;  Procedure getComNum; var Reg: TRegistry; begin   Reg := TRegistry.Create(KEY_READ or KEY_WOW64_64KEY);   try     Reg.RootKey := HKEY_LOCAL_MACHINE;     if Reg.OpenKey('\SOFTWARE\FPanel', false) then     begin       ComNum := Reg.ReadString('ComNum');       Reg.CloseKey;     end;   finally     Reg.Free;   end; end;  Procedure InitCPUUsage; begin   if @NtQuerySystemInformation = nil then NtQuerySystemInformation := GetProcAddress(GetModuleHandle('ntdll.dll'), 'NtQuerySystemInformation');   status := NtQuerySystemInformation(SystemBasicInformation, @SysBaseInfo, SizeOf(SysBaseInfo), nil);   if status <> 0 then exit; end;  function CPUUsed: integer;    function Li2Double(x: LARGE_INTEGER): Double;   begin     Result := x.HighPart * 4.294967296E9 + x.LowPart   end;  begin   result := 0;   status := NtQuerySystemInformation(SystemTimeInformation, @SysTimeInfo, SizeOf(SysTimeInfo), nil);   if status <> 0 then Exit;   status := NtQuerySystemInformation(SystemPerformanceInformation, @SysPerfInfo, SizeOf(SysPerfInfo), nil);   if status <> 0 then Exit;    dbIdleTime := Li2Double(SysPerfInfo.liIdleTime) - Li2Double(liOldIdleTime);   dbSystemTime := Li2Double(SysTimeInfo.liKeSystemTime) - Li2Double(liOldSystemTime);   dbIdleTimePercent := dbIdleTime / dbSystemTime * 100;    liOldIdleTime := SysPerfInfo.liIdleTime;   liOldSystemTime := SysTimeInfo.liKeSystemTime;    if (dbIdleTimePercent / SysBaseInfo.bKeNumberProcessors) < 0 then result := 0 else   result := round(abs(100-(dbIdleTimePercent / SysBaseInfo.bKeNumberProcessors)));  end;  function RAMUsed: byte; var RamStats: TMemoryStatus; begin   GlobalMemoryStatus(RamStats);   result := ramStats.dwMemoryLoad; end;  procedure con2com; var ComFN:string; begin   CloseHandle(hCom);   ComFN := '\\.\COM' + comNum;   hCom := CreateFile(PWidechar(ComFN), GENERIC_WRITE or GENERIC_READ, 0, nil, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, 0); //Change COM1 to the comport you are using...   if hCom = INVALID_HANDLE_VALUE then  Exit;    SetupComm(hCom,100,100);    GetCommState(hCom,DCB);   with DCB do begin     BaudRate:=9600;     ByteSize:=8;     Parity:=NoParity;     StopBits:=OneStopBit;   end;   SetCommState(hCom,DCB);    GetCommTimeouts(hCom,Timeouts);   with TimeOuts do begin     ReadIntervalTimeout := 1;     ReadTotalTimeoutMultiplier := 0;     ReadTotalTimeoutConstant := 1;     WriteTotalTimeoutMultiplier := 2;     WriteTotalTimeoutConstant := 2;   end;   SetCommTimeouts(hCom,Timeouts); end;  procedure TFPService.ServiceExecute(Sender: TService); var NumberWritten:LongWord;     twoBytes:array[0..1] of byte;  begin while not Terminated do   begin     twoBytes[0] := round(CPUUsed / 8.33);     twoBytes[1] := round(RAMUsed / 8.33);     if WriteFile(hCom, twoBytes, 2, NumberWritten, nil) = False then con2com;     Sleep(250);     ServiceThread.ProcessRequests(False);   end; end;  procedure TFPService.ServiceCreate(Sender: TObject); begin  initCPUUsage;  getComNum;  con2com; end;  end. 

Для установки службы файл FP_servicel.ехе нужно запустить с параметром /install с правами администратора. После этого нужно создать строковый ключ ComNum в реестре, со значением номера COM-порта, на котором «висит» наш ланчпад по пути HKEY_LOCAL_MACHINE\SOFTWARE\FPanel, в моем случае значение ComNum = «12». После этого достаточно запустить службу FPService через оснастку «Службы». В следующий раз она будет стартовать сама.
Видео работы:

Архив Delphi проекта+готовый.ехе
Спасибо за внимание.

ссылка на оригинал статьи http://habrahabr.ru/post/206392/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *