Напишете програма за com порт. Пренасочване на данни от COM порт към мрежата

Серийните портове са обичани от разработчиците заради лесната им поддръжка и използване.

И разбира се, писането в конзолата на терминалната програма е добре, но аз искам мое собствено приложение, което чрез натискане на клавиш на екрана изпълнява действията, от които се нуждаете;)

В тази статия ще опиша как да работите с com порт на езика C++.

Решението е просто, но по някаква причина работещ пример не беше намерен веднага. За SIM го запазвам тук.

Разбира се, можете да използвате междуплатформени решения като QSerial - библиотека в Qt, вероятно ще го направя, но в бъдеще. Сега говорим за "чист" Windows C++. Ще пишем на визуално студио. Имам 2010 г., въпреки че това не играе никаква роля ...

Създайте нов конзолен Win32 проект.

Включете заглавни файлове:

#включи #включи използване на пространство от имена std;

Ние декларираме манипулатор на com порт:

HANDLE hSerial;

Правя го глобално, така че не трябва да се тревожа за указатели, когато го предавам на функции.

int _tmain(int argc, _TCHAR* argv) (

Не понасям Windows стила на програмиране. Те нарекоха всичко по свой начин и седят и се радват ...

Сега магията на декларирането на низ с името на порта. Въпросът е, че той не може да трансформира самия char.

LPCTSTR sPortName = L"COM1";

Работата със серийни портове в Windows работи като с файл. Отваряне на първия com порт за писане/четене:

HSerial = ::CreateFile(sPortName,GENERIC_READ | GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);

Проверка на функционалността:

If(hSerial==INVALID_HANDLE_VALUE) ( if(GetLastError()==ERROR_FILE_NOT_FOUND) ( cout<< "serial port does not exist.\n"; } cout << "some other error occurred.\n"; }

Сега трябва да конфигурирате параметрите на връзката:

DCB dcbSerialParams = (0); dcbSerialParams.DCBlength=sizeof(dcbSerialParams); if (!GetCommState(hSerial, &dcbSerialParams)) ( cout<< "getting state error\n"; } dcbSerialParams.BaudRate=CBR_9600; dcbSerialParams.ByteSize=8; dcbSerialParams.StopBits=ONESTOPBIT; dcbSerialParams.Parity=NOPARITY; if(!SetCommState(hSerial, &dcbSerialParams)) { cout << "error setting serial port state\n"; }

На msdn се препоръчва първо да получите параметрите и след това да ги промените. Ние все още се учим, така че правим както е поискано.

Сега нека декларираме низа, който ще предадем, и променливите, необходими за това:

Char data = "Здравейте от C++"; // низ за предаване на DWORD dwSize = sizeof(данни); // размер на този низ DWORD dwBytesWritten; // тук ще бъде броят на действително прехвърлените байтове

Изпращане на низ. Нека ви напомня, че примерът е най-простият, така че не правя специални проверки:

BOOL iRet = WriteFile(hSerial,data,dwSize,&dwBytesWritten,NULL);

Реших също да покажа размера на низа и броя байтове, изпратени за контрол:

Коут<< dwSize << " Bytes in string. " << dwBytesWritten << " Bytes sended. " << endl;

В края на програмата правим безкраен цикъл на четене на данни:

Докато(1) ( ReadCOM(); ) върне 0; )

Сега функцията за четене:

Void ReadCOM() ( DWORD iSize; char sReceivedChar; while (true) ( ​​​​ReadFile(hSerial, &sReceivedChar, 1, &iSize, 0); // вземете 1 байт, ако (iSize > 0) // отпечатайте cout, ако нещо е получено<< sReceivedChar; } }

Това всъщност е целият пример.

  • Анализирайте активността на серийния порт

    Serial Port Monitor може да се свърже към COM порт, дори ако той вече е отворен от някое приложение, и веднага да започне да го наблюдава. Всички данни, преминаващи през наблюдавания COM порт, ще бъдат показани в нашата програма за наблюдение. Тъй като всичко се записва в реално време, ще можете незабавно да забележите проблеми. За сравняване на данни има функция за синхронизиран избор на едни и същи IRP в различни изгледи.

    Освен това можете да пренасочите всички данни за наблюдение към определен файл или да копирате всички записани данни в клипборда. Serial Port Monitor ви дава възможност да прихващате и записвате всички входно/изходни контролни кодове на сериен порт (IOCTL), да наблюдавате всички техни данни и параметри. Можете да запазите всяка сесия за наблюдение и да я заредите следващия път, ако е необходимо.

  • Наблюдавайте множество портове в рамките на една и съща сесия

    Serial Port Monitor има уникална функционалност за наблюдение на множество COM портове едновременно. Сега можете да събирате данни за това как приложенията взаимодействат с два или повече порта и паралелно с множество устройства в рамките на една и съща сесия. Получените и изпратени данни от мониторинга ще бъдат представени (записани) в отделен дневник в реда, в който са получени, което значително улеснява анализа.

  • Различни опции за преглед на получените данни

    Можете да преглеждате данните за мониторинг в 4 режима едновременно: таблица, ред, дъмп или терминал, всеки от които предлага различен начин за представяне на записаните серийни данни. Serial Port Monitor ви позволява да избирате филтри за наблюдение, като по този начин ви спестява време и ви позволява да наблюдавате само събитията, които представляват интерес. В настройките можете да изберете данните за показване: двоичен, ASCII, конфигуриране на порта. Всички настройки на дисплея могат да бъдат приложени директно в текущия процес на наблюдение.

  • Емулирайте трансфер на данни към серийно устройство

    Можете да изпращате данни в различни формати (низ, двоичен, осмичен, десетичен, шестнадесетичен, смесен) към наблюдавания сериен порт, сякаш са изпратени директно от контролираното приложение, като използвате функцията за терминален режим на монитор на сериен порт. По този начин можете да наблюдавате реакцията на управляваното серийно устройство към някои специални команди и данни.

  • Пълна поддръжка за Modbus протокол за данни (RTU и ASCII)

    С помощта на новите филтри за наблюдение на сериен порт ще можете да дешифрирате и анализирате Modbus данни. Програмата ще помогне не само да се установи връзка между RS485/422/232 устройства, но и да извърши ефективен анализ на преминаващите данни.

  • Повторете и сравнете сесиите за наблюдение

    Serial Port Monitor предоставя уникална възможност за повторно възпроизвеждане на сесията от приложението към порта за най-добър анализ на текущите процеси. Ще можете да наблюдавате реакцията на серийния порт към преминаването на същите данни, като по този начин повишавате ефективността на мониторинга. Освен това имате възможност да сравнявате множество сесии за наблюдение и автоматично да проследявате разликата между тях.

За това как красиво да се представят данните, изпратени от Arduin към Serial. По мое мнение, момчетата предложиха много красиво решение, което от една страна изглежда доста просто, а от друга ви позволява да получите отличен резултат с минимални усилия.

В коментарите към статията беше изразено съжаление, че такова решение няма да работи под Firefox и беше изразена идеята, че "все пак можете да напишете прост уеб сървър с html изход, базиран на това нещо." Тази идея ме „закачи“, бързо търсене в Google не даде готово решение и реших да реализирам идеята сам. И ето какво излезе от това.

Внимание! Предложеното решение в никакъв случай не трябва да се счита за завършено. За разлика от серийния проектор на Amperka, това е концепция, демонстрация на възможен подход, работещ прототип и нищо повече.

Преди време направих проект, в който използвах вградените акселерометри в смартфон с Android, за да управлявам серво, свързани към Arduino. След това за тези цели използвах Scripting Layer за Android (SL4A) и проектите RemoteSensors. Оказва се, че стандартната библиотека на python включва пакета BaseHTTPServer, с който да създадете уеб услуга в python е задача в няколко реда код.

Нямаше сензори за Arduino под ръка, така че използвах вътрешния термометър, вграден в Arduino Uno като източник на показаната информация. Доколкото разбирам, не е много точен и изобщо не е предназначен за измерване на температурата на околната среда, но ще стане за прототипиране.

След кратко гугъл, ето скица за ардуино:

// източник: https://code.google.com/p/tinkerit/wiki/SecretThermometer long readTemp() (дълъг резултат; // Четене на температурен сензор спрямо 1,1 V референтен ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(MUX3); delay(2); // Изчакайте Vref да се установи ADCSRA |= _BV(ADSC); // Преобразуване докато (bit_is_set(ADCSRA,ADSC)); резултат = ADCL; резултат |= ADCH<<8; result = (result - 125) * 1075; return result; } void setup() { Serial.begin(115200); } int count = 0; void loop() { String s = String(count++, DEC) + ": " + String(readTemp(), DEC); Serial.println(s) delay(1000); }
Тази скица отваря COM порт, настройва го на 115200 бода и след това записва текущата стойност на вградения термометър в него всяка секунда. (Не ме питайте в какви мерни единици е дадена температурата - за описаната задача не е важно). Тъй като стойността не се променя много активно, за по-добра видимост на промените в данните, номерът на реда се показва преди температурата.

За да проверите дали уеб сървърът ще издава само цели редове, а не части от тях, тъй като чете от COM порта, редът
Serial.println(s)
е заменен от
за (int i=0; i< s.length(); i++){ Serial.print(s.charAt(i)); delay(200); } Serial.println("");
тези. формираният низ не се извежда към серийния порт изцяло, а символ по символ, с паузи от 200 ms.

Като начало беше написан много прост прототип на уеб сървър (по-долу е разглобен на части):
# -*- кодиране: utf-8 -*- #-- базирано на: https://raw.githubusercontent.com/Jonty/RemoteSensors/master/remoteSensors.py SERIAL_PORT_NAME = "COM6" SERIAL_PORT_SPEED = 115200 WEB_SERVER_PORT = 8000 време за импортиране , BaseHTTPServer, urlparse import serial ser = None def main(): global ser httpd = BaseHTTPServer.HTTPServer(("", WEB_SERVER_PORT), Handler) #-- решение за получаване на IP адрес, на който се обслужва import socket s = socket.socket( socket.AF_INET, socket.SOCK_DGRAM) s.connect(("google.co.uk", 80)) sData = s.getsockname() print "Обслужване на "%s:%s"" % (sData, WEB_SERVER_PORT) сер = serial.Serial(SERIAL_PORT_NAME, SERIAL_PORT_SPEED, timeout=0) httpd.serve_forever() class Handler(BaseHTTPServer.BaseHTTPRequestHandler): # Деактивиране на регистриране на DNS търсения def address_string(self): return str(self.client_address) def do_GET(self): self.send_response(200) self.send_header("Content-type", "application/x-javascript; charset=utf-8") self.end_headers() try: while True: new_serial_line = get_full_line_from_serial() ако new_serial_line не е None: self.wfile.write(new_serial_line) self.wfile.write("\n") self.wfile.flush() освен socket.error, e: print "Клиентът е прекъснат.\n" captured = "" def get_full_line_from_serial(): """ връща пълен ред от serial или None Използва глобални променливи "ser" и "captured" """ global captured part = ser.readline() if part: captured += part parts = captured.split("\n", 1); if len(parts) == 2: captured = parts return parts return None if __name__ == "__main__": main()
Нека разбием сценария част по част.

Тъй като това е прототип, всички основни параметри на работа (името на COM порта, неговата скорост, както и номера на TCP порта, на който ще работи уеб сървърът) са посочени директно в изходния текст:
SERIAL_PORT_NAME="COM6" SERIAL_PORT_SPEED=115200 WEB_SERVER_PORT=8000
Разбира се, можете да организирате четенето на тези параметри от командния ред. Например с помощта на модула argparse това става много бързо, лесно и гъвкаво.

В този случай потребителите на Windows трябва да открият в диспечера на устройства името на COM порта, към който е свързан Arduin. За мен беше "COM6". Потребителите на други операционни системи трябва да използват инструментите на тяхната операционна система. Нямам никакъв опит с MacOS и не съм работил с COM портове на Linux, но там най-вероятно ще бъде нещо като "/dev/ttySn".

Следва дефиницията на глобална променлива, към която ще бъде обвързан екземпляр от класа Serial, който в python отговаря за работата с COM порта:
ser = Няма
В редица
httpd = BaseHTTPServer.HTTPServer(("", WEB_SERVER_PORT), манипулатор)
създава се уеб сървър, който ще слуша за заявки на посочения порт WEB_SERVER_PORT. И тези заявки ще бъдат обработени от екземпляр на класа Handler, описан по-долу.

Следните редове са малък "хак" за показване на IP адреса, на който действително работи работещият уеб сървър:
#-- заобиколно решение за получаване на IP адрес, на който обслужва import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(("google.co.uk", 80)) sData = s.getsockname() print "Serving at "%s:%s"" % (sData, WEB_SERVER_PORT)
Доколкото разбирам, няма друг начин да разберете това IP. И без това знание как ще имаме достъп до нашия сървър от браузъра?

Следователно трябва да отворите сокет и да се свържете със сайта на Google, за да извлечете информация за вашия собствен IP адрес от атрибутите на този сокет.

Малко по-надолу се отваря COM портът и всъщност се стартира уеб сървърът:
ser = serial.Serial(SERIAL_PORT_NAME, SERIAL_PORT_SPEED, timeout=0) httpd.serve_forever()
Това е последвано от описание на класа, който отговаря за обработката на заявки, получени от работещия уеб сървър:
клас Handler(BaseHTTPServer.BaseHTTPRequestHandler):
Това е наследник на класа, вграден в модула BaseHTTPServer, в който е достатъчно да замените само метода do_GET

Тъй като това все още е прототип, сървърът ще бъде „доволен“ от всяка заявка - без значение какъв URL е поискан от него, той ще даде на клиента всички данни, прочетени от COM порта. Следователно в Handler.do_GET той незабавно отговаря с код за успех и необходимите заглавки:
self.send_response(200) self.send_header("Content-type", "application/x-javascript; charset=utf-8") self.end_headers()
след което се стартира безкраен цикъл, в който се прави опит да се прочете цял ред от COM порта и, ако опитът е успешен, да се прехвърли към уеб клиента:
while True: new_serial_line = get_full_line_from_serial() ако new_serial_line не е None: self.wfile.write(new_serial_line) self.wfile.write("\n") self.wfile.flush()
В проекта, който беше взет за основа, този безкраен цикъл беше „увит“ в блок за опит ... освен, с помощта на който трябваше внимателно да се справи с прекъсването на връзката. Може би в Android (базовият проект е разработен за него) това работи добре, но не ми се получи под Windows XP - когато връзката беше прекъсната, възникна някакво друго изключение, което така и не се научих как да прихвана. Но, за щастие, това не попречи на уеб сървъра да работи нормално и да приеме следните заявки.

Функцията за получаване на цял низ от COM порт работи на същия принцип като създателите на Serial Projector:

  • има някакъв глобален буфер, който съхранява всичко, което се чете от COM порта
  • при всяко извикване на функцията тя се опитва да прочете нещо от COM порта
  • ако успее, тогава
    • той добавя това, което току-що е прочетено, към посочения глобален буфер
    • се опитва да раздели глобалния буфер на най-много две части чрез знака за край на реда
    • ако успее, тогава връща първата част на извикващата процедура и използва втората част като нова стойност на глобалния буфер
  • ако няма нови данни в COM порта или знакът за край на реда не е намерен, тогава функцията връща None:
captured = "" def get_full_line_from_serial(): """ връща пълен ред от serial или None Използва глобални променливи "ser" и "captured" """ global captured part = ser.readline() if part: captured += part parts = captured.split("\n", 1); if len(parts) == 2: captured = връщане на части връщане на части Няма
В резултат на това се оказа така:

Вижда се, че в браузъра се появяват редове, които се четат от COM порта. Не разбирам нищо от уеб интерфейса: JavaScript, Ajax, CSS и DOM са тъмна гора за мен. Но ми се струва, че за програмисти, създаващи уеб интерфейси, това би трябвало да е напълно достатъчно, за да преобразуват този изход в същата красива картина, която произвежда серийният проектор на Amperka. Според мен задачата е да се създаде javascript скрипт, който осъществява достъп до уеб сървъра, чете поток от него и извежда последния прочетен ред на правилното място в уеб страницата.

За всеки случай реших да играя на сигурно и се опитах сам да направя първото приближение. Не много задълбочено търсене в Google предполага, че всъщност за такива цели са се използвали поне WebSockets или сървърно изпратени събития. Открих това, което ми се стори добро използване на изпратените от сървъра събития и реших да използвам тази технология.

Забележка! Това не изглежда най-доброто решение, тъй като тази технология не работи в Internet Explorer 8, нито в браузъра, вграден в Android 2.3.5. Но работеше поне във Firefox 39.0, така че не "рових" повече.

От гледна точка на скрипта на Python, промените под Изпратени от сървъра събития са напълно незначителни:

  • е необходимо да се замени вида на данните, предоставени на клиента:
    линия
    self.send_header("Content-type", "application/x-javascript; charset=utf-8")
    заменен от
    self.send_header("Content-type", "text/event-stream")
  • и също така преди реда, прочетен от COM порта, вмъкнете префикса "данни:" и добавете още един знак за нов ред:
    линии
    self.wfile.write(new_serial_line) self.wfile.write("\n")
    заменен от
    self.wfile.write("данни: " + new_serial_line) self.wfile.write("\n\n")

Всичко останало може да остане непроменено, но...

Първо създадох файл index.html със следното съдържание:

заглавка




Най-интересното в него е линията
който формира място за извеждане на следващия ред от COM порта и javascript скрипт

който всъщност чете потока от уеб сървъра и извежда прочетената информация на посоченото място.

Възнамерявах да отворя този файл в браузър, например от диск или от някакъв друг уеб сървър, но това не проработи: при отваряне на страница от диск javascript скриптът веднъж получи достъп до работещия уеб сървър на Python и веднага прекъсна връзката. Не разбрах защо се случва това и предположих, че това може да е някакъв вид защита на браузъра срещу различни атаки. Вероятно не му харесва, че самата страница се отваря от един източник, а скриптът чете данни от друг източник.

Затова беше решено да се промени уеб сървърът на Python, така че да обслужва и тази html страница. Тогава ще се окаже, че и страницата, и потокът се четат от един и същи източник. Не знам дали моето предположение за сигурността се оказа правилно или нещо друго, но с тази реализация всичко работи както трябва.

Разбира се, трябва само да промените класа на манипулатора на заявка Handler:
class Handler(BaseHTTPServer.BaseHTTPRequestHandler): # Деактивиране на регистриране на DNS търсения def address_string(self): return str(self.client_address) def do_GET(self): if self.path == "/" или self.path == "/index .html": self.process_index() elif self.path == "/get_serial": self.process_get_serial() else: self.process_unknown() def process_index(self): self.send_response(200) self.send_header("Съдържание -type", "text/html; charset=utf-8") self.end_headers() self.wfile.write(open("index.html")).read()) self.wfile.write("\n\ n") self.wfile.flush() def process_get_serial(self): self.send_response(200) self.send_header("Content-type", "text/event-stream") self.end_headers() try: while True: new_serial_line = get_full_line_from_serial() ако new_serial_line не е None: self.wfile.write("data: " + new_serial_line) self.wfile.write("\n\n") self.wfile.flush() освен socket.error, e : print "Клиентът е прекъснат.\n" def process_unknown(self): self.send_response(404)
Тази опция предполага, че уеб сървърът ще отговори само на две заявки: "/index.html" (дава html кода на страницата) и "/get_serial" (дава безкраен поток от низове, прочетени от COM порта). На всички други заявки ще бъде отговорено с код 404.

Тъй като index.html се обслужва от уеб сървъра на Python, той може да бъде леко модифициран чрез указване на относителен вместо абсолютния адрес на потока от низове от COM порта:
низ
var source = new EventSource("http://192.168.1.207:8000/")
заменен от
var source = new EventSource("/get_serial")
В резултат на това се оказа така:

Тук реших да спра. Струва ми се, че красивото проектиране на страница вече трябва да е доста просто. Но аз не владея HTML или CSS, така че нека някой друг да го направи. Видях задачата си в това да покажа, че създаването на уеб услуга, която изпраща данни от COM порт, изглежда, не е никак трудно.

Повтарям още веднъж: представеният код не е цялостно решение, което може да бъде „пуснато в производство“. Това е само прототип, който показва фундаментален подход към решаването на проблема.

Върху какво още може да се работи:

  • първо, четенето на данни от COM порта в скрипта на python става много "тромаво" - всъщност има постоянно запитване "има ли нещо свежо?". Този подход, разбира се, натоварва процесора и едно ядро ​​на моя компютър е заето на 100%.
    Като решение може да се използва блокиращо четене с изчакване. За да направите това, достатъчно е да посочите ненулева стойност (в секунди) като таймаут при отваряне на COM порт, например:
    ser = serial.Serial(SERIAL_PORT_NAME, SERIAL_PORT_SPEED, timeout=0.03)
    Освен това в описанието на модула pySerial има три примера за създаване на мост: „TCP/IP – сериен мост“, „Еднопортов TCP/IP – сериен мост (RFC 2217)“ и „Многопортов TCP /IP - сериен мост (RFC 2217)" - можете да видите как професионалистите решават подобни проблеми.
  • второ, само един клиент може да получава данни. Докато страницата не бъде затворена на първия клиент, не можете да се свържете с този сървър и да получите стойности на втория компютър. От една страна, това вероятно е правилно: има само един COM порт и има няколко консуматора - кой от тях трябва да даде линията за четене? Ако смятате, че отговорът на този въпрос трябва да бъде „всички“, тогава ето моите мисли по въпроса. Струва ми се, че проблемът не може да бъде решен само чрез използване на "честен" многопоточен уеб сървър (например някое Tornado или Flask), който може да обслужва заявки от няколко уеб клиента едновременно. Тъй като не можете да отворите COM порт от всяка нишка и да четете от нея - в този случай данните от COM порта ще отидат само до една нишка/процес. Ето защо според мен е необходимо сървърната част да се раздели на две части:
    • zmq сървър, който работи с COM порт, чете редове от него и ги изпраща чрез PUB сокет до всички заинтересовани потребители
    • уеб сървърът на python вместо да се свързва към COM порт се свързва към zmq сървър и получава данни от него
    Ако не сте запознати с библиотеката ZMQ (ZeroMQ), тогава можете да използвате обикновени TCP / IP или UDP сокети вместо това, но силно бих препоръчал да се запознаете с ZMQ, защото тази библиотека прави много лесно решаването на подобни проблеми. Струва ми се, че с помощта на ZMQ решението ще се побере в максимум 20 реда. (Не мога да устоя на писането: дори ако не планирате да решавате описаната задача, но работата ви е свързана с многопоточно / многопроцесно програмиране с обмен на данни между нишки / процеси, разгледайте по-отблизо тази библиотека - може би това е, за което сте говорили толкова дълго мечтано)
  • потокът от данни все още е еднопосочен - от COM порта към уеб браузъра. Все още не можете да изпращате данни от браузъра към Arduino. Струва ми се, че тази задача също не е много трудна и за разлика от предишната може да бъде решена само
    • използване на многонишков сървър
    • усъвършенстване на метода Handler.do_GET, така че да приема GET заявки с параметри и да изпраща стойностите на някои от тях към COM порта
    По мое мнение, ако искате да напишете пълноценен аналог на уеб базирания монитор на сериен порт, вграден в Arduino IDE, не е толкова трудно. Лично за себе си виждам трудността само в създаването на нормален фронтенд.
  • все още не е възможно да зададете името на COM порта и параметрите на неговата работа през браузъра. От една страна, това изглежда логично: как може потребител от другата страна на нашата планета да знае към кой COM порт и с каква скорост е свързан arduino? Но уеб сървърът на Python, работещ на същия компютър, знае със сигурност. Но ако все пак е желателно да се даде възможност на потребителя да промени името на COM порта или параметрите на неговата работа, тогава отново това може лесно да бъде решено чрез прецизиране на метода Handler.do_GET
  • python е необходим за стартиране на сървъра. Това обикновено не е трудно, но ако по някаква причина това не може да стане или не искате, тогава pyInstaller може да се притече на помощ. С него скриптът на Python може да се компилира в един изпълним файл (в случая на Windows - в .exe), който лесно се копира на компютъра, към който е свързан arduino.
    Може би най-доброто решение би било да използвате езика Go в този случай. Доколкото знам, той решава по-добре проблема със създаването на файл за "разпространение".
В заключение: може да възникне въпросът: „не е ли по-лесно да се реши този проблем чрез някакъв готов облак?“ Защо не публикувате данни, които могат да се четат от COM порт в облака, а клиентите просто да имат достъп до съответната услуга в облака? Вероятно такова решение също има право на съществуване, но преди да се приложи такова решение, трябва да се отговори на следните въпроси:
  • Има ли готови уеб услуги, които ми позволяват да публикувам данни със скоростта/честотата, от която се нуждая? Има ли сред тях безплатни или сте готови да платите съответните пари?
  • готови ли сте за това, че при падане на облака или връзка с него, ще останете без данни
  • Не ви ли притеснява, че за да прехвърлят данни от една стая в друга, те ще прекосят два пъти океана или половин континент?


Зареждане...
Връх