44015

Розробка програмного забезпечення спортивної статистики, соціальної мережі та веб-сервісу прийому замовлень для служб таксі

Дипломная

Информатика, кибернетика и программирование

Розробка універсального алгоритму і програмного забезпечення для автоматичного підрахунку фізичних вправ і ведення спортивної статистики, створення відповідної соціальної мережі, здатної витримувати високі навантаження на ресурси (більше 1000 запитів за секунду). Розробка веб-сервісу прийому замовлень для служб таксі

Украинкский

2013-11-09

6.68 MB

13 чел.

ВСТУП

Основним двигуном прогресу в розвитку web-технологій є проекти, орієнтовані на багатомільйонну аудиторію і здатні витримувати високе навантаження на ресурси (більше 1000 запитів за секунду). По-перше, для реалізації подібних проектів необхідно максимально ефективно оптимізувати програмний код, по-друге, використовувати потужну апаратуру.

На поточному етапі розвитку IT-технологій спостерігається тенденція завоювання міцних позицій смартфонами. Основними їх перевагами є зручність, наявність ряду вбудованих функцій (GPS-навігація, акселерометр, гіроскоп, мобільний зв'язок, фото- і відео-камера та ін.), а також те, що вони здатні виконувати більшість функцій стаціонарних комп'ютерів і ноутбуків. З кожним днем все більше людей відкривають для себе переваги смартфонів, тому багато високонавантажених Інтернет-сервісів зараз працюють в зв'язці з мобільними пристроями.

У магістерській роботі розробляється проект ведення спортивної статистики, першим компонентом якого є додаток для смартфонів сімейства iPhone, основною функцією якого є збір статистики, другим веб-сервер, який синхронізується з додатком і видає дані на сайт.

Другим проектом є система прийому замовлень для служб таксі, яка передбачає високе навантаження на сервер і забезпечує збір необхідних статистичних даних.

 Наукова новизна. Запропоновані організація системного програмного забезпечення, що виконує автоматичний підрахунок виконаних фізичних вправ (підтягувань, віджимань і т. п.), і створення спеціальної соціальної мережі із специфічним набором функцій, орієнтованих на ведення спортивної статистики. Запропонована система прийому замовлень для служб таксі і збору специфічних у цій галузі даних.

 Мета магістерської роботи. Розробка універсального алгоритму    і програмного забезпечення для автоматичного підрахунку фізичних вправ і ведення спортивної статистики, створення відповідної соціальної мережі, здатної витримувати високі навантаження на ресурси (більше 1000 запитів за секунду). Розробка веб-сервісу прийому замовлень для служб таксі.

 Виходячи з мети були поставлені наступні задачі:

- дослідження вбудованого акселерометра, розробка алгоритму автоматичного підрахунку фізичнх вправ і його практична реалізація у вигляді програмного продукту для смартфонів сімейства iPhone;

- налаштування сервера, здатного витримувати високі навантаження на ресурси (більше 1000 запитів в секунду), розробка API для взаємодії з додатком для смартфону і створення соціальної мережі з необхідним набором функцій;

- поширення програмного продукту і тестування його роботи і роботи сервера на реальній аудиторії з високим навантаженням на ресурси;

- дослідження принципу роботи існуючих служб таксі і аналіз можливості прискорення і автоматизації процесу замовлення і пошуку вільної машини і розробка відповідного веб-сервісу прийому замовлень.

 Реалізація і упровадження результатів роботи. Під час науково-дослідної практики був укладений договір з компанією Apple Inc. на отримання права розробляти і розміщувати додатки в офіційному магазині. Розроблений проект SportDiary був розміщений в App Store у вересні 2012 року.

 Апробація роботи: основні наукові результати роботи доповідались на науково-технічній конференції «Информационные управляющие системы и компьютерный мониторинг» (Донецьк, ДонНТУ – 2012), та багаторазово на спеціалізованих семінарах.

 

 Структура і об'єм магістерської роботи:

Робота складається зі вступу, 5 глав, виводу, переліку посилань (15 найменувань), містить 100 сторінок тексту, 50 рисунків, 3 таблиці і 2 доповнення.

 У першому розділі приводиться огляд аналогічних розробок додатків збору спортивної статистики для мобільних пристроїв і огляд аналогічних проектів по прийому замовлень для служб таксі.

 У другому розділі розробляється загальна структура проекту ведення спортивної статистики. Приводиться дослідження вбудованого в мобільний пристрій акселерометра і розробка алгоритму автоматичного підрахунку виконаних фізичних вправ. Описана розробка кінцевої версії мобільного додатку і використаних технологій.

 У третьому розділі приводиться розробка специфічної соціальної мережі, призначеної для користувачів мобільного додатка SportDiary. Проведений огляд використаних технологій і налаштування сервера. Описаний процес забезпечення взаємодії соціальної мережі з мобільним застосуванням. Проведено розміщення проекту у відкритому доступі і його тестування на реальних користувачах.

 У четвертому розділі описана розробка демонстраційної версії веб-сервісу прийому замовлень для служб таксі на базі технологій, використаних для розробки соціальної мережі.

 У п'ятому розділі приведені основні положення охорони праці, проведений розрахунок кондиціонування, освітлення і шуму в приміщенні.

1. ОГЛЯД ІСНУЮЧИХ РОЗРОБОК ВЕБ-СЕРВІСІВ ВЕДЕННЯ СПОРТИВНОЇ СТАТИСТИКИ І ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ ПРИЙОМУ ЗАМОВЛЕНЬ ДЛЯ СЛУЖБ ТАКСІ

Аналогічні проекти по веденню спортивної статистики можна розділити на 3 категорії:

  1.  додатки, які орієнтовані тільки на ведення статистики і планування режиму тренувань;
  2.  додатки, які орієнтовані на ведення статистики з дистанційних вправ (біг, їзда на велосипеді і т. п.) з використанням вбудованого GPS-навігатору;
  3.  додатки, які орієнтовані на ведення статистики з силових вправ (віджимання, підтягування і т. п.) з використанням вбудованого акселерометра, гіроскопа і відео-камери.

Оскільки у рамках магістерської роботи додаток розробляється для смартфонів сімейства iPhone, аналіз існуючих проектів проводиться серед аналогічних пристроїв.

Додатки, орієнтовані тільки на ведення статистики і планування режиму тренувань передбачають можливість введення даних тільки вручну для будь-яких типів вправ. В основному подібні проекти розраховані на аудиторію людей, які займаються в тренажерному залі. У таких додатках не передбачений автоматичний підрахунок повторень і не використовуються вбудовані акселерометр, гіроскоп, GPS-навигатор і камера.

Додатки, орієнтовані на ведення статистики з дистанційних вправ, використовують вбудований GPS-навігатор (рідше акселерометр),  за допомогою якого здійснюється збір необхідних даних (швидкість, пройдена відстань, географічні координати і т. д.). На основі отриманих даних будується певна статистика.

Одним з найбільш відомих і багатофункціональних проектів  є додаток "Endomondo Sports Tracker". Основними його функціями є:

  •  зображення пройденого маршруту на карті;
  •  реєстрація і збереження різних статистичних даних (пройдена дистанція, час, швидкість, втрачені калорії і т. п.). Більшість даних збираються за допомогою вбудованого в  iPhone акселерометра;
  •  синхронізація даних з сервером;
  •  публікація даних в соціальній мережі.

У проект "Endomondo Sports Tracker" також входить соціальна мережа, в якій кожен користувач може ділитися своїми маршрутами і статистичними даними з іншими користувачами.

Інтерфейс програми, на якому зображений приклад пройденого маршруту і зібраних статистичних даних, приведений на рисунку 1.1.

Рисунок 1.1 - Інтерфейс додатка "Endomondo Sports Tracker" з приведеною статистикою

Додатки, орієнтовані на ведення статистики силових вправ найбільш близькі за змістом до проекту, що розроблюється в магістерській роботі. Для підрахунку повторень в них використовуються можливості вбудованого акселерометра, рідше – відеокамери. Недоліком подібних застосувань є те, що статистику можливо вести тільки для фіксованого набору вправ. Додавати свої вправи не можна.

Одним з перших і найбільш успішних застосувань, в яких використовується автоматичний підрахунок фізичних вправ, є додаток "Push-ups".

Принцип його роботи полягає в тому, що реєстрація кожного нового повторення здійснюється за допомогою вбудованої передньої відео-камери. Людина повинна покласти телефон на підлогу, і віджиматися, ледве торкаючись телефону грудьми. Камера фіксує затемнення екрану і рахує їх за повторення. Цей додаток розрахований на ведення статистики, але не включає вбудовану соціальну мережу. Істотним недоліком додатка є те, що він розрахований тільки на підрахунок віджимань з причини специфіки алгоритму. Інтерфейс додатка, на якому зображена зібрана статистика з віджимань, приведений на рисунку 1.2.

Вбудований в iPhone акселерометр використовувався для підрахунку фізичних вправ в додатку "FitFu" (на момент оформлення магістерської роботи додаток був видалений з продажу). У цьому проекті використовувалася власна соціальна мережа, в якій користувачі могли публікувати свої досягнення. Проте істотним недоліком додатка є те, що він реалізований у вигляді гри, і не передбачає ведення статистики і додавання власних вправ.  Автоматичний підрахунок може виконуватися тільки для фіксованої кількості вправ, серед яких немає багатьох базових і найбільш популярних.

Існує безліч програмних продуктів з прийому замовлень і збору статистичних даних для служб таксі. Більшість з цих проектів комерційні і закриті.

Рисунок 1.2 - Інтерфейс додатка "Push-ups" з приведеною статистикою

У рамках магістерської роботи пропонується усі проекти з прийому замовлень для служб таксі класифікувати за наступними критеріями:

  •  по наявності підтримки функцій GPS :

а) з підтримкою функцій GPS. У таких проектах робиться замовлення через сайт або мобільний додаток за допомогою мітки на карті і пошук вільних машин, які відмічені на карті;

б) без підтримки функцій GPS. У таких проектах замовлення робиться по телефону або за допомогою SMS-сообщения і пошук таксистів – без задіювання відповідних API;

  •  по організації роботи :

а) повністю автоматизовані. Замовлення і пошук машини ведеться автоматично, без участі роботи диспетчера;

б) напівавтоматизовані. Частина роботи покладається на диспетчера (наприклад, прийом замовлення по телефону), частина роботи виконується автоматично (наприклад, пошук вільної машини);

  •  за способом вибору вільної машини :

а) з використанням черги. Вибирається перша машина в черзі, в якій відзначаються таксисти;

б) з використанням пошуку найближчої машини. Вибирається машина, яка знаходиться в даний момент щонайближче до адреси, на яку робився виклик;

в) по вибору клієнта. Клієнт сам вибирає найбільш зручну йому машину з усіх можливих, які відмічені на карті.

Одним з аналогічних проектів є місцеве таксі міста Донецьк. Принцип його роботи полягає в наступному. Клієнт робить дзвінок диспетчерові. Замовлення може робитися тільки по телефону. Диспетчер отримує адресу, на яку треба послати машину і вводить цю адресу в базу поточних замовлень. Диспетчери діляться на дві категорії:

1) диспетчери, які спілкуються з клієнтами і приймають замовлення;

2) диспетчери, які спілкуються з таксистами і беруть участь в розподілі замовлень.

На локальному сервері знаходиться система обробки замовлень, яка розподіляє замовлення між таксистами.  Якщо на замовлення, що поступило, немає жодного вільного таксиста, диспетчер просить клієнта почекати, поки з'явиться вільна машина. Якщо в потрібному районі є вільний таксист, замовлення відправляється йому.

Для кращої організації розподілу замовлень існує черга таксистів. Нове замовлення, що поступило, вирушає першій в цій черзі машині. Кожному таксистові видається мобільний Java-додаток, за допомогою якого він може відзначатися в черзі і отримувати повідомлення про замовлення, що поступили.

Диспетчери, які працюють з таксистами, спілкуються з ними по рації, контролюють порядок роботи і допомагають в екстрених ситуаціях.

Недоліком цієї схеми роботи прийому замовлень полягає в наступному:

- не оптимальна витрата бензину (на замовлення часто прибуває вільний таксист, який знаходиться не щонайближче до необхідної адреси);

- часто виникають ситуації, коли пошук вільної машини затягується більш ніж на 1 хвилину;

- в пікові моменти завантаження служби таксі часто виникають ситуації перевантаження лінії зв'язку і пропуску дзвінків.

Другим аналогічним проектом є "Яндекс Таксі".  Користуватися послугами "Яндекс Таксі" можна як через браузер, так і через мобільні додатки для смартфонів iPhone і Android. На момент написання магістерської роботи сервіс працював тільки в Москві.

У сервісі "Яндекс Таксі" виклик таксі автоматизований і здатний обслуговувати усі замовлення навіть в пікові моменти, до тих пір, поки є вільні машини. Інтерфейс сервісу приведений на рисунку 1.3.

Рисунок 1.3 - Інтерфейс веб-сервісу "Яндекс Таксі"

Принцип роботи "Яндекс Таксі" полягає в тому, що система сама підбирає вільний автомобіль який знаходиться щонайближче до клієнта. Для замовлення машини необхідно вказати адресу, на яку викликається машина, і пункт призначення, а також час, коли повинне прибути таксі. Клієнт отримує інформацію про те, скільки приблизно триватиме поїздка з урахуванням пробок. Також можна вказати особливі вимоги до автомобіля (кондиціонер, салон для некурящих або курящих і т. п). Клієнт бачить на карті усі вільні на даний момент таксі в режимі реального часу. На рисунку 1.4. зображений останній етап виклику таксі.

Рисунок 1.4 - Виклик таксі за вказаною адресою за допомогою сервісу "Яндекс Таксі"

Клієнтові надається інформація про викликаний автомобіль: служба таксі і її рейтинг, тариф, дані про водія, номер його телефону, колір і марка автомобіля, а також його номер. Для підтвердження виклику необхідно ввести номер свого мобільного телефону або зайти в Яндекс під своїм акаунтом. Під час виклику на карті можна спостерігати рух таксиста в режимі реального часу.

Слід виділити наступні переваги такого роду системи виклику таксі :

- швидкий підбір ближчої машини;

- можливість клієнта самостійно вибирати характеристики автомобіля;

- здатність системи витримувати високе навантаження на ресурси;

- економія коштів на послугах диспетчера і купівлі додаткового обладнання (рацій).

На даний момент існує тенденція переходу на автоматизовані системи прийому замовлень.

2. РОЗРОБКА СТРУКТУРИ ПРОЕКТУ ВЕДЕННЯ СПОРТИВНОЇ СТАТИСТИКИ І ОФФЛАЙН-ВЕРСІЇ МОБІЛЬНОГО ДОДАТКУ ДЛЯ IPHONE

2.1. Розробка загальної структури проекту ведення спортивної статистики

Проект SportDiary включає мобільний додаток для смартфонів iPhone, який працює в зв'язці із спеціально розробленою соціальною мережею. Узагальнена схема роботи проекту SportDiary зображена на рисунку 2.1.

Мобільне застосування виконує наступні функції:

1) автоматичний підрахунок кількості повторень виконаної фізичної вправи за допомогою вбудованого в мобільний пристрій акселерометра;

2) ведення статистики по кожній вправі (зібрані дані по вправах зберігаються в локальну базу даних і видаються користувачеві у вигляді графіку);

3) редагування статистики вручну;

4) видалення і додавання будь-яких вправ;

5) зберігання базових персональних даних користувача (логін і пароль для входу в соціальну мережу, ім'я, фото);

6) синхронізація даних з сервером за наявності Інтернету;

7) перегляд даних будь-якого користувача соціальної мережі за наявності Інтернету;

8) пошук по користувачах;

9) можливість підписки на користувача.

Соціальна мережа sportdiary.net виконує наступні функції:

1) перегляд даних, синхронізованих з мобільним додатком, перегляд статистики по кожній вправі;

2) пошук користувачів;

3) перегляд даних будь-якого користувача;

4) коментування вправ користувачів.

Рисунок 2.1 - Узагальнена схема роботи проекту SportDiary

Структура проекту SportDiary приведена на рисунку 2.2.

Рисунок 2.2 - Структура проекту SportDiary

Проект розрахований тільки на користувачів смартфонів iPhone, починаючи з 4 серії. Мобільний додаток SportDiary розрахований на аудиторію, яка займається фитнесом і виконує стандартні фізичні вправи. Автоматичний підрахунок повторень виконаної фізичної вправи робиться за допомогою вбудованого в мобільний пристрій акселерометра.

Отримані дані зберігаються в локальну базу даних SQLite. Статистика по кожній вправі може бути виведена на дисплей смартфону.

При підключенні iPhone до інтернету оновлені дані з локальної бази даних синхронізуються з сервером і записуються в базу даних SQL на сервері.

На сервері зберігаються усі необхідні документи, скрипти і бази даних, які забезпечують функціонування спеціально розробленої соціальної мережі, яка орієнтована на ведення спортивної статистики.

Доступ до соціальної мережі можна отримати як з мобільного застосування, так і безпосередньо на веб-сайті www.sportdiary.net. В соціальній мережі можна проглянути дані будь-якого користувача: фотографію, ім'я, усі вправи, статистику по кожній вправі.

2.2. Дослідження вбудованого в мобільний пристрій акселерометра і розробка алгоритму автоматичного підрахунку виконаних фізичних вправ

Серед усіх існуючих мобільних пристроїв смартфони iPhone можна віднести до одних з самих вдосконалених. Вбудовані в  iPhone акселерометр і гіроскоп відкривають широкі можливості у використанні функцій руху і обертання смартфону.

У рамках магістерської роботи було поставлено завдання розробити універсальний алгоритм для підрахунку фізичних вправ за допомогою вбудованого в мобільний пристрій акселерометра. Під універсальністю мається на увазі те, що один і той же алгоритм виконуватиме підрахунок усіх підтримуваних фізичних вправ (підтягувань, віджимань, скручувань, присідань, віджимань на брусах) і різних варіацій цих вправ (підтягувань різними хватами, тих же самих вправ з додаванням ваги і т. п.).

Кожна з вище вказаних вправ не передбачає ніяких обертань тіла, тому для розробки алгоритму не використовується гіроскоп, а використовується тільки акселерометр.

Принцип дії акселерометра полягає в тому, що при будь-якому русі смартфону реєструються відповідні прискорення по трьом осях X, Y, Z [4].

На рисунку 2.3. зображено, яка вісь відповідає певній орієнтації смартфону в просторі і рух в якому напрямі відповідає руху в позитивну або негативну піввісь. У цьому полягає спрощений принцип дії акселерометра.

За початкове прискорення по певній осі, якщо та знаходиться перпендикулярно опорі, прийнято прискорення g = 9.81 м/с2.

Рисунок 2.3 - Спрощений принцип роботи вбудованого в iPhone акселерометра

У iPhone прийняті наступні орієнтації пристрою в просторі:

1) Portrait Up – смартфон розташований у вертикальному положенні, нижня кнопка дивиться вниз, прискорення по осі Y дорівнює - g, по інших осях – нуям ;

2) Portrait Down – смартфон розташований у вертикальному положенні, нижня кнопка дивиться вгору, прискорення по осі Y дорівнює +g, по інших осях – нулям;

3) Landscape Right – смартфон розташований в горизонтальному положенні, нижня кнопка дивиться вправо, прискорення по осі X дорівнює - g, по інших осях – нулям;

4) Landscape Left – смартфон розташований в горизонтальному положенні, нижня кнопка дивиться вліво, прискорення по осі X дорівнює +g, по інших осях – нулям;

5) Face Up – смартфон розташований паралельно поверхні землі, екран дивиться вгору, прискорення по осі Z дорівнює - g, по інших осях – нулям;

6) Face Down – смартфон розташований паралельно поверхні землі, екран дивиться вгору, прискорення по осі Z дорівнює +g, по інших осях – нулям.

При якому-небудь переміщенні смартфону в просторі значення прискорення по осях X, Y, Z змінюються.

У всіх вище вказаних вправ існує загальна особливість – одне повторення вправи складається з руху вгору-вниз, або вниз-вгору. Таким чином, існує можливість об'єднати підрахунок усіх вправ в один алгоритм.

Для дослідження сигналу від акселерометра і розробки алгоритму було використано безкоштовне застосування для iPhone Sensor Monitor, яке надає сигнал від різних датчиків смартфону у вигляді графіків. Інтерфейс додатка Sensor Monitor приведений на рисунку 2.4.

Поле Freq. служить для вибору частоти опитування акселерометра, яка вимірюється в Гц. Чим вище частота опитування акселерометра, тим більш точно видається графік сигналу. У додатку Sensor Monitor можливо встановити 4 значення частоти опитування акселерометра : 30, 60, 90 і 120 Гц. Для реєстрації одного повторення вправи навіть частота 30 Гц є надмірною, оскільки навіть в найшвидшому темпі дуже складно зробити навіть 2 повторення будь-які з вправ. Тому подальші дослідження акселерометра проводяться на частоті 30 Гц.

Лівіше за поле Freq. виводяться значення прискорення по кожній з осей X, Y, Z.  Значення змінюються з вказаною частотою.

На тимчасовій діаграмі зображений графік сигналу, що поступає від акселерометра, по трьох осях.

Рисунок 2.4 - Інтерфейс додатка Sensor Monitor

Нижня частина екрану відведена для тих же даних, що поступають від гіроскопа. Ці дані надалі ніяк не враховуватимуться.

Далі необхідно досліджувати сигнал для п'яти базових фізичних вправ: підтягувань, віджимань, віджимань на брусах, присідань і скручувань і виявити загальні закономірності.

Графік сигналу від акселерометра при виконанні підтягувань зображений на рисунку 2.5.

При виконанні цієї вправи iPhone знаходився в орієнтації Portrait. Для фіксації смартфону досить покласти його в кишеню або тримати в руках. Як видно з графіку, дані по осях X і Z мало коливаються, і дуже складно виділити які-небудь пікові області. Значення прискорення по осі Х приблизно прагне до нуля. Тому для реєстрації повторень в даному випадку необхідно аналізувати дані, отримані по осі Y.

Рисунок 2.5 - Графік сигналу від акселерометра при виконанні підтягувань

До початку вправи значення прискорення по осі Y дорівнює - 1g. Перед  першим підняттям корпусу вгору над перекладиною прискорення різко зменшується приблизно до - 1.6g, і потім збільшується приблизно до - 0.4g. При наступних трьох повтореннях спостерігається приблизно такий же характер вихідного сигналу, але без початкового падіння.

Через високу частоту опитування акселерометра в сигналі є присутніми високочастотні перешкоди, проте їх амплітуда практично не виділяється і ніяк не впливає на характер вихідного сигналу. Деякі стрибки амплітуд пов'язані з самим рухом корпусу, але вони також не сильно спотворюють форму вихідного сигналу.

Таким чином, можна зробити висновок, що для реєстрації початку вправи треба простежити перше падіння прискорення - 1g до приблизно - 1.5g. Значення можна узяти із запасом < - 1.3g. Для реєстрації кожного повторення необхідно виділити послідовність двох рухів – вгору-вниз. Рух вгору визначається по стрибку прискорення > - 0.7g, рух вниз – по падінню прискорення нижче - 1.3g. Усі значення прискорення беруться з невеликим запасом приблизно 10%, з урахуванням того, що кожна людина виконує вправу з різною силою і швидкістю.

Далі необхідно провести дослідження сигналу від акселерометра при виконанні віджимань. Графік приведений на рисунку 2.6.

Рисунок 2.6 - Графік сигналу від акселерометра при виконанні

віджимань

При виконанні віджимань значення прискорення по осях X і Y не сильно відхиляються від початкового положення. Найбільш зручною для реєстрації повторень є вісь Z.

До початку вправи значення прискорення по осі Z дорівнює - 1g. Перед  першим опусканням корпусу вниз прискорення різко збільшується приблизно до - 0.4g, і потім зменшується приблизно до - 2.3g. При наступних чотирьох повтореннях спостерігається приблизно такий же характер вихідного сигналу.

Через високу частоту опитування акселерометра в сигналі є присутніми високочастотні перешкоди, проте їх амплітуда практично не виділяється і ніяк не впливає на характер вихідного сигналу. Деякі стрибки амплітуд пов'язані з самим рухом корпусу, але вони також не сильно спотворюють форму вихідного сигналу.

Таким чином, можна зробити висновок, що для реєстрації початку вправи треба простежити перше збільшення прискорення - 1g до приблизно - 0.4g. Значення можна узяти із запасом > - 0.7g. Для реєстрації кожного повторення необхідно виділити послідовність двох рухів – вниз-вгору. Рух вниз визначається по падінню прискорення нижче - 1.3g, рух вгору – по стрибку прискорення вище - 0.7g. Усі значення прискорення беруться з невеликим запасом приблизно 10%, з урахуванням того, що кожна людина виконує вправу з різною силою і швидкістю.

Далі необхідно провести дослідження сигналу від акселерометра при виконанні віджимань на брусах. Графік приведений на рисунку 2.7.

При виконанні віджимань на брусах значення прискорення по осях X і Z не сильно відхиляються від початкового положення. Найбільш зручною для реєстрації повторень є вісь Y.

До початку вправи значення прискорення по осі Y дорівнює - 1g. Перед  першим опусканням корпусу вниз прискорення різко збільшується приблизно до - 0.6g, і потім зменшується приблизно до - 2.2g. При наступних чотирьох повтореннях спостерігається приблизно такий же характер вихідного сигналу.

Через високу частоту опитування акселерометра в сигналі є присутніми високочастотні перешкоди, проте їх амплітуда практично не виділяється і ніяк не впливає на характер вихідного сигналу. Деякі стрибки амплітуд пов'язані з самим рухом корпусу, але вони також не сильно спотворюють форму вихідного сигналу.

Рисунок 2.7 - Графік сигналу від акселерометра при виконанні

віджимань на брусах

Таким чином, можна зробити висновок, що для реєстрації початку вправи треба простежити перше збільшення прискорення - 1g до приблизно - 0.6g. Значення можна узяти із запасом > - 0.7g. Для реєстрації кожного повторення необхідно виділити послідовність двох рухів – вниз-вгору. Рух вниз визначається по падінню прискорення нижче - 1.3g, рух вгору - по стрибку прискорення вище - 0.7g. Усі значення прискорення беруться з невеликим запасом приблизно 10%, з урахуванням того, що кожна людина виконує вправу з різною силою і швидкістю.

Далі необхідно провести дослідження сигналу від акселерометра при виконанні присідань. Графік приведений на рисунку 2.8.

При виконанні присідань значення прискорення по осях X і Z не сильно відхиляються від початкового положення. Найбільш зручною для реєстрації повторень є вісь Y.

Рисунок 2.8 - Графік сигналу від акселерометра при виконанні

присідань

До початку вправи значення прискорення по осі Y дорівнює - 1g. Перед  першим опусканням корпусу вниз прискорення різко збільшується приблизно до - 0.6g, і потім зменшується приблизно до - 2.4g. При наступних двох повтореннях спостерігається приблизно такий же характер вихідного сигналу.

Через високу частоту опитування акселерометра в сигналі є присутніми високочастотні перешкоди, проте їх амплітуда практично не виділяється і ніяк не впливає на характер вихідного сигналу. Деякі стрибки амплітуд пов'язані з самим рухом корпусу, але вони також не сильно спотворюють форму вихідного сигналу.

Таким чином, можна зробити висновок, що для реєстрації початку вправи треба простежити перше збільшення прискорення - 1g до приблизно - 0.6g. Значення можна узяти із запасом > - 0.7g. Для реєстрації кожного повторення необхідно виділити послідовність двох рухів – вниз-вгору. Рух вниз визначається по падінню прискорення нижче - 1.3g, рух вгору – по стрибку прискорення вище - 0.7g. Усі значення прискорення беруться з невеликим запасом приблизно 10%, з урахуванням того, що кожна людина виконує вправу з різною силою і швидкістю.

Останньою стандартною вправою є скручування. Графік сигналу від акселерометра при виконанні цієї вправи приведений на рисунку 2.9.

Рисунок 2.9 - Графік сигналу від акселерометра при виконанні

скручувань

При виконанні скручувань найбільш зручною для реєстрації повторень є вісь Z.

До початку вправи значення прискорення по осі Z дорівнює 1g. Перед  першим підняттям корпусу прискорення різко збільшується приблизно до +1.6g, і потім зменшується приблизно до - 1g. При наступних двох повтореннях спостерігається приблизно такий же характер вихідного сигналу.

Через високу частоту опитування акселерометра в сигналі є присутніми високочастотні перешкоди, проте їх амплітуда практично не виділяється і ніяк не впливає на характер вихідного сигналу. Деякі стрибки амплітуд пов'язані з самим рухом корпусу, але вони також не сильно спотворюють форму вихідного сигналу.

Таким чином, можна зробити висновок, що для реєстрації початку вправи треба простежити перше збільшення прискорення від +1g до приблизно +1.6g. Значення можна узяти із запасом > +1.3g. Для реєстрації кожного повторення необхідно виділити послідовність двох рухів – вгору-вниз. Рух вгору визначається по падінню прискорення нижче 0.7g, рух вниз – по стрибку прискорення вище +1.3g. Усі значення прискорення беруться з великим запасом (запас при скручуваннях можна було зробити меншим), з урахуванням того, що кожна людина виконує вправу з різною силою і швидкістю і з урахуванням відповідності іншим вправам.

Оскільки смартфон може знаходитися в руках або кишені користувача в шести різних орієнтаціях (Portrait Up, Portrait Down, Landscape Right, Landscape Left, Face Up, Face Down), то і реєстрація повторень може проводитися за допомогою різних осей – X, Y або Z. Значення прискорень в різних орієнтаціях ніяк не відрізняються по амплітуді і частоті. Визначальним в підрахунку повторень є чинник вибору орієнтації, а отже і осі.

Як можна переконатися з усіх досліджених вправ, характер сигналу в усіх випадках практично однаковий. Значення амплітуд відрізняються трохи. Для кожної вправи рух вниз і вгору можна визначити як відхилення амплітуди прискорення від початкового значення на +0.3g.

Якщо положення смартфону відрізняється від усіх стандартних орієнтацій в просторі, і початкові значення по усіх осях приміром прагнуть по модулю до 0.5g, то погрішність є не критичною і близько 95% повторень реєструються. Єдиною перешкодою для правильної реєстрації повторень є погана фіксація смартфону в кишені або руках. Якщо користувач під час виконання вправи якось повертає або трясе iPhone, це може бути зараховано за зайві повторення.

Також на практиці слід досліджувати неправильне повторення вправ, тобто виконання вправ не в повну силу. Приклад графіку для такої вправи наведений на рисунку 2.10.

Рисунок 2.10 - Графік сигналу від акселерометра при неправильному виконанні віджимань на брусах

При неправильному виконанні вправ відхилення амплітуди прискорення по модулю становить менше ніж 0.3g. Таким чином це можна врахувати в організації алгоритму і не зараховувати неправильне виконання вправ.

Узагальнена блок-схема підрахунку кількості повторень виконаної фізичної вправи приведена на рисунку 2.11.

Рисунок 2.11 - Узагальнена блок-схема підрахунку кількості повторень виконаних фізичних вправ с використанням акселерометру

У блоці "Визначення орієнтації смартфону" отримуються значення прискорень по усіх осях і фіксується найбільш відповідна орієнтація. Залежно від орієнтації смартфону в просторі вибирається вісь, згідно якої реєструватимуться повторення.

Кожні 50 мс поступають нові значення від акселерометра. Якщо значення по вибраній осі відрізняється від початкового більш ніж на 0.3g, це прискорення реєструється як рух вгору або вниз. Якщо перший рух був вгору, то після появи руху вниз лічильник кількості повторень збільшується на 1. Якщо перший рух був вниз, то після появи руху вгору лічильник кількості повторень збільшується на 1. Підрахунок повторень триває, поки тривають нові рухи. Якщо по події п'яти секунд не було жодного руху, підрахунок зупиняється.

Для зручності користувача початок і кінець вправи супроводжуються звуковим сигналом. Існує безліч звукових бібліотек з різними функціональними можливостями [8]. У проекті SportDiary використовується бібліотека AVFoundation.

Фрагмент початкового коду алгоритму автоматичного підрахунку вправ включений в опис класу TrainingViewController і приведений в додатку А.

2.3. Розробка кінцевої версії мобільного додатку і огляд використаних технологій

 Розробка iPhone-додатків повинна проводитись на комп’ютерах сімейства Macintosh в середовищі розроблення програмного забезпечення XCode. У рамках магістерської роботи використовується версія XCode 4.

Пакет XCode підтримує мови програмування C, C++, Objective-C, Objective-C++, Java, AppleScript, Python та Ruby з різними моделями програмування. Для розробки проекту SportDiary використовується тільки Objective-C, оскільки він є стандартною мовою програмування для розробки додатків під iPhone.

Objective-C підмножина С є об’єктно-орієнтованою мовою програмування корпорації Apple. Основною відмінністю Objective-C є концепція відправлення повідомлень об’єктам, на що об’єкт відповідає виконанням певних дій. Для прикладу можна привести графічний інтерфейс, коли головний контролер відправляє повідомлення View нарисувати коло певного розміру, на що View рисує коло. Objective-C підтримує всі оператори мови С та білшість концепцій об’єктно-орієнтованого програмування.

Програма XCode призначена для розробки додатків для OS X (операційна система для комп’ютерів сімейства Macintosh), та iOS (операційна система для iPad, iPhone і iPod touch) і включає в себе низку інструментів для розробки програмного забезпечення, таких як документація розробника, додаток, що використовується для розробки графічних інтерфейсів, пакет моніторингу, та інші.

Операційні системи OS X та iOS архітектурно дуже схожі, за відміною того, що в ОС для смартфонів верхнім шаром є Cocoa Touch Framework, а в OS X — Cocoa Framework. Структури операційних систем OS X та iOS зображені на рисунку 2.1.

Рисунок 2.12 — загальна архітектура операційних систем Mac OS X та

iPhone OS

Оскільки проект розроблюється для iPhone, далі буде розглядатися тільки архітектура iOS.

 Нинжній рівень Core OS є фундаментом операційної системи. Він відповідає за організацію пам’яті,  файлову систему, мережі та інші задачі, відповідні за апаратне забезпечення. Core OS складається з наступних компонентів:

  •  ядро OS X;
    •  Mach 3.0;
    •  BSD;
    •  сокети;
    •  рівень захисту;
    •  управління потужністю;
    •  Keychain;
    •  сертифікати;
    •  файлова система;
    •  Bonjour.

Наступний рівень Core Services є абстракцією над нижнім рівнем. Він забезпечує базовий доступ до сервісів iOS та включає наступні компоненти:

  •  колекції;
    •  адресна книга;
    •  мережі;
    •  доступ до файлів;
    •  SQLite;
    •  Core Location;
    •  веб-сервіси;
    •  потоки;
    •  Preferences;
    •  URL-утіліти.

Рівень Media забезпечує мультимедійні сервіси, що можна використовувати в  додатках для iPhone та iPad. Він складається з наступних компонентів:

- Core Audio;

  •  OpenGL;
    •  Audio Mixing;
    •  аудіозапис;
    •  програш відео;
    •  JPG, PNG, TIFF;
    •  PDF;
    •  Quartz;
    •  Core Animation;
    •  OpenGL ES.

Рівень Cocoa Touch являє собою абстрактний шар, що представляє різноманітні бібліотеки для програмування iPhone та iPad, такі як наступні:

  •  події Multi-Touch;
    •  контроль Multi-Touch;
    •  акселерометр;
    •  ієрархія View;
    •  локалізація;
    •  сигнали;
    •  Web Views;
    •  People Picker;
    •  Image Picker;
    •  контролери [7].

У програмуванні iOS усі функції на кожному з рівнів забезпечуються за допомогою низки бібліотек. У проекті SportDiary використовуються усі рівні окрім нижчого.

 Проект SportDiary розроблюється в пакеті розроблення програмного забезпечення XCode 4 в операційній системі Mac OS Lion.

Програма написана згідно зі схемою проектування MVC (model-view-controller), тобто модель-представлення-контролер. Концепція MVC зображена на рисунку 2.13.

Згідно з концепцією MVC програмний продукт повинен складатися з трьох компонентів: контролеру, представлення та моделі. Представлення — це графічний вид, тобто інтерфейс програми. Модель — це абстрактне представлення даних. Контролер — компонент який здійснює комунікацію між цими двома компонентами та оброблює необхідні дані.

Рисунок 2.13 — концепція проектування Model-View-Controller

Програмний продукт взаємодіє із користувачем через контролер. Додаток також взаємодіє з базою даних через абстрактну модель.

Отже розроблений проект SportDiary складається з наступних компнонентів:

1. Графічний інтерфейс (приклад одного View приведений на рисунку 2.14). Графічний інтерфейс збудований згідно з рекомендаціями компанії Apple та є зручним та зрозумілим у використанні [10].

2. Контролер для кожного View.

3. Локальна база даних, в якій зберігаються статистичні дані та деякі дані користувача.

4. Модель, в якості якої використовуються встроєні в бібліотеку libsqlite3 функції.

Рисунок 2.14 — Приклад View з проекту SportDiary

Головними компонентами програми є:

1. Автоматичний лічильник повторювань вправи. Алгоритм цього лічильнику є універсальним і може підраховувати віджимання, підтягування, віджимання на брусах, присідання, скручування, та різні варіації цих вправ, таких як підтягування з вузьким хватом, широким хватом та інші.

2. Складання статистики по кожній вправі та зберігання значень в базі даних SQLite. Відбудовування графіку статистики по кожній вправі (рисунок 2.15).

Статистика складається за наступним принципом. Усі повторювання вправи за день сумуються та зберігаються до бази даних. Якщо значення за сьогоднішній день вже зберігається в базі даних, то нове значення додається до попередньго і новий результат зберігається до бази даних. Коли користувач переходить до View, на якому представлений графік статистики, із бази даних вибираються значення для цієї вправи за кожен день та будується графік, кожна точка в якому представляє кількість повторювань вправи за один день. Статистика представляється за весь період тренувань.

Якщо користувач взагалі не виконував цієї вправи, графік пустий.

Рисунок 2.15 — Графік статистики по обраній вправі

3. Синхронізація даних статистики та користувача з сервером. Для цього використовуються вбудовані функції роботи з URL для організації post та get-запросів.

На сервері були створені спеціальні API для взаємодії із додатком смартфону. Наприклад, для отримання кількості точок на графіку статистики з серверу необхідно сформувати строку запросу наступним чином:

NSString * request = [NSString stringWithFormat:@"http://sportdiary.net/api/getchartdata_ios_counts.php?rand=%i&id=%@&caption_id=%@", num, userID, exerciseID].

 Для тестування взаємодії програми із сервером проект був завантажений на  iPhone 4 та протестований в локальному режимі та в режимі онлайн через Wi-Fi.

  Повна структура програми зображена у форматі storyboard на рисунку 2.16.

Рисунок 2.16 — Структура програми SportDiary у форматі storyboard

 Проект являє собою сукупність View, пов’язаних один с одним за допомогою елементу Segue. Кожному View відповідає свій контролер. Статистика зберігається в базі даних, доступ до якої здійснюється за допомогою  функцій бібліотеки libsqlite3.

Назви та призначення основних контролерів приведені в таблиці 2.1.

Таблиця 2.1 - Призначення основних контроллерів в проекті

Назва контролера

Основне призначення контролера

WelcomeViewController

Стартове вікно в додатку. Визначення підключення до Інтернету. Перша синхронізація з сервером

HelpViewController

Довідка по використанню додатка

MasterViewController

Список усіх вправ. Додавання і видалення вправ

DetailViewController

Графік статистики з вибраної вправи

TrainingViewController

Автоматичний підрахунок вправ з використанням вбудованого акселерометра

EditRepsViewController

Редагування статистики вручну

ProfileViewController

Профіль користувача і його персональні дані (ім'я, фотографія, логін і пароль для входу до соціальної мережі за допомогою сайту)

UsersMasterViewController

Список користувачів соціальної мережі

UsersDetailViewController

Персональна сторінка користувача з можливістю перегляду статистики по будь-якій вправі і підписки на цього користувача

  При завантаженні додатка SportDiary з'являється стартове меню, з якого можна перейти на довідку Help або увійти до головної частини програми. Окрім цього стартовий вид (View) виконує функцію визначення наявності інтернету, а також початковій синхронізації.

Для перевірки наявності Інтернету на сервер вирушає get-запит, у відповідь на який приходить JSON-массив з єдиним параметром status, який оповіщає, був запит успішним або ні. Якщо у відповідь приходить значення 'ok ', то наявність Інтернету підтверджується. Якщо у відповідь приходить інше значення, або якщо у відповідь не приходить JSON масив, то додаток працюватиме в offline-режимі, оскільки немає Інтернету. Прапор наявності Інтернету зберігається в парі ключ-значення NSUserDefaults. Клас NSUserDefaults - простий спосіб зберігати базові значення, такі як об'єкти NSString, NSNumber, BOOL і т. д. [1]

Якщо це перша перевірка на наявність Інтернету і Інтернет доступний, проводиться реєстрація користувача в соціальній мережі. Для цього також використовується get -запрос за певною адресою, на що приходить відповідь у вигляді JSON-массива з полями login і password. Логін і пароль нового користувача зберігаються в базі даних на сервері.

Отриманий логін і пароль зберігаються в локальній базі даних додатка для подальшої ідентифікації користувача при відправленні різних запитів на сервер.

Також можлива попередня синхронізація з сервером. Якщо в попередніх сеансах додаток працював в офлайн-режимі і будь-які дані були змінені, усі змінені дані будуть відправлені на сервер, якщо є підключення до Інтернету.

За допомогою Tab Bar додаток умовно розділений на 5 основних блоків:

1) Exercises - вправи і статистика користувача;

2) My Profile - основна інформація про користувача (ім'я, фотографія, логін і пароль для входу в соціальну мережу);

3) News - новини усіх користувачів і друзів;

4) People - список усіх користувачів, друзів, лідерів, а також елемент пошуку по користувачах;

5) Help - коротка довідка про використання додатка і деякі рекомендації по правильному виконанню вправ.

Статистичні дані і деякі дані про користувача зберігаються в локальній базі даних SQLite. Структура бази даних в приведена на рисунку 2.17 в спеціальному плагині для Mozilla Firefox SQLite Manager.

Рисунок 2.17 - Структура бази даних додатка SportDiary

База даних називається ExercisesDB.sqlite. У базі зберігається три таблиці:

- Exercises – інформація про вправи;

- Statistics – інформація про статистику по вправах;

- Personal Data – персональні дані користувача.

Призначення полів в таблиці Exercises наступне:

- Name – назва вправи, зберігається у вигляді рядка;

- UniqueID – MD5-кодування імені вправи, зберігається у вигляді рядка. Використовується для зв'язку з таблицею Statistics і прискорення операцій з базою даних.

Призначення полів в таблиці Statistics наступне:

- Unique ID – кодування імені вправи, зберігається у вигляді рядка;

- ExerciseDate – остання дата виконання вправи, зберігається у вигляді рядка;

- Reps – кількість повторень вправ в певний день, зберігається у вигляді цілого числа.

Призначення полів в таблиці PersonalData :

- UserName – ім'я користувача, зберігається у вигляді рядка;

- UserLogin – логін користувача для входу в соціальну мережу, зберігається у вигляді рядка;

- UserPassword – пароль користувача для входу в соціальну мережу, зберігається у вигляді рядка;

- IsNameChanged – індикатор зміни імені користувача, потрібний для синхронізації, зберігається у вигляді цілого числа;

- IsPhotoChanged – індикатор зміни фотографії користувача, потрібний для синхронізації, зберігається у вигляді цілого числа;

- SyncDate – дата останньої синхронізації з сервером;

- UserID - ідентифікатор користувача, іншими словами, його порядковий номер реєстрації в соціальній мережі, зберігається у вигляді цілого числа.

У вкладці Exercises користувач може зайти в стандартні вправи, створити власні і видаляти вправи. При натисненні на будь-яку вправу, відкривається відповідна статистика.

Статистика може бути відредагована двома способами. Перший – виконати вправу з використанням автоматичного підрахунку вправи за допомогою вбудованого акселерометра. Другий – відредагувати статистику вручну. Можна змінити кількість виконаних вправ за будь-який день. Якщо встановити значення 0, це значення віддалиться з бази даних. Інтерфейс редагування статистики приведений на рисунку 2.18.

При кожному новому редагуванні бази даних змінені дані відразу ж відправляються на сервер. Таким чином усі зміни можна поспостерігати на сайті протягом декількох секунд. Якщо Інтернету в даний момент немає, то усі дані заносяться тільки в локальну базу даних і зводиться прапор необхідності синхронізації, який зберігається в стандартних налаштуваннях додатка. Коли Інтернет доступний і цей прапор дорівнює одиниці, відбувається синхронізація з сервером. Таким чином забезпечується збереження даних, і усі дані у будь-якому випадку синхронізуються з сервером, навіть якщо користуватися додатком в офлайн-режиме довгий час.

Рисунок 2.18 - Інтерфейс редагування статистики

 

Усі дані відправляються на сервер за допомогою POST-запиту. Нижче приведений фрагмент програмного коду відправлення даних на сервер за допомогою POST-запиту:

NSData *postData = [params dataUsingEncoding: NSUTF8StringEncoding allowLossyConversion: NO];

               

                               NSString *postLength = [NSString stringWithFormat:@"%d", [postData length]];

               

                               int num = rand();

               

                               NSString * reqURl = [NSString stringWithFormat:@"http://sportdiary.net/api/sync_d.php?rand=%i", num];

                               NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString : reqURl]

                                                                                                                                             cachePolicy: NSURLRequestReloadIgnoringLocalCacheData

                                                                                                                                     timeoutInterval :5.0];

               

                               [request setHTTPMethod:@"POST"];

                               [request setValue: postLength forHTTPHeaderField:@"Content-Length"];

                               [request setValue:@"application /x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];

                               [request setHTTPBody: postData];

               

                               NSData *responseData = [NSURLConnection sendSynchronousRequest: request returningResponse: nil error: nil];

 В об'єкті postData зберігаються статистичні дані, які були додані пізніше за останню дату синхронізації. Об'єкт responseData приймає відповідь від сервера у форматі JSON-масива. Якщо єдиний параметр status рівний 'ok ', то синхронізація вважається успішною і остання дата змінюється на сьогоднішню. Якщо ж відправка даних була з помилкою, то синхронізація відкладається на наступний раз.

Максимальний час очікування відповіді від сервера встановлюється рівним п'яти секундам. Це зроблено на випадок повільного Інтернету користувача. Проте дане значення узято з великим запасом. У 95% тестування програми відповідь складала не більше секунди. Лише у пікові моменти завантаження сервера під час тестування утілитою ab він міг наближатися до 3 секунд.

Додаток SportDiary також підтримує можливість використання соціальної мережі, для якої виділено три вкладки: My Profile, News, People.

У вкладці My Profile можна змінити свої ім'я і фотографію, які будуть видні іншим користувачам соціальної мережі. Також тут зображені логін і пароль, під якими можна зайти в мережу на сайті sportdiary.net.

Нове ім'я користувача заноситься в локальну базу даних, а також вирушає на сервер за допомогою GET-запросу.

Нове фото користувача може бути вибране тільки з фотографій, збережених в альбомі iPhone, і вирушає на сервер, на відміну від імені,  за допомогою POST-запросу, оскільки об'єм даних набагато більший.

У вкладці People можна подивитися зареєстрованих в соціальній мережі користувачів. У свою чергу усі користувачі розділені на три підгрупи:

- All – усі зареєстровані користувачі;

- Favourites – друзі, на яких підписався користувач;

- Top – користувачі, які виконали найбільше повторень стандартної вправи за поточний день.

Інтерфейс новин соціальної мережі зображений на рисунку 2.19.

Рисунок 2.19 - Інтерфейс новин соціальної мережі в мобільному додатку SportDiary

Вкладка News аналогічна. У ній відображуються останні новини від користувачів All і Favourites. Дані, якими заповнюється Table View отримується у форматі JSON-массива в результаті запиту до сервера. JSON-массив зберігає наступну інформацію по кожному користувачеві: id користувача, його ім'я і новину. Фотографія користувача в дрібному форматі 75*75 пікселів отримується за допомогою звичайного запиту за адресою, по якій зберігається це фото.

 Для того щоб проект працював без збоїв, його необхідно протестувати в менеджері профілей у профілі витоку пам’яті.

Тестування проекту на витоки пам’яті зображене на рисунку 2.20.

Рисунок 2.20 — Тестування проекту на витоки пам’яті

 Як видно із малюнку 3.6 в проекті немає витоків пам' яті. Іноді при виділенні пам' яті під рядка можуть бути витоки блоків пам' яті не більш ніж 200 байт, але сморід не мають ніякого впливу на правильність роботи програми, оскільки в операційній системі є менеджер автоматичного підрахунку посилань, який виправляє витоки незначного розміру. Коли об'єкт має бути знищений через обнулення його лічильника посилань, Objective-C автоматично відправляє об'єкту повідомлення dealloc і розробник не повинен за цим стежити [5].

3. РОЗРОБКА СПЕЦИФІЧНОЇ СОЦІАЛЬНОЇ МЕРЕЖІ, ПРИЗНАЧЕНОЇ ДЛЯ КОРИСТУВАЧІВ МОБІЛЬНОГО ДОДАТКА SPORTDIARY

 

 3.1. Налаштування сервера і огляд використаних технологій

Для підтримки соціальної мережі з великою (у перспективі) кількістю користувачів необхідно вибрати потужний і надійний сервер, який здатний витримувати високе навантаження на ресурси – більше 1000 запитів за секунду.

Раніше в ході роботи над високонавантаженими веб-сервісами було розроблено три додатки для соціальних мереж, які набрали аудиторію        130 000 користувачів.

Спочатку для підтримки цих додатків використовувався VPS-сервер, наданий українським підприємством HostLife. Основні характеристики цього сервера :

- процесор: 900 Мгц;

- ОЗП: 1 Гб;

- жорсткий диск: 20 Гб;

- трафік необмежений;

- ціна 30 у. е.

Проте із зростанням аудиторії приблизно до 100 000, сервер перестав справлятися з навантаженням. Завантаження оперативної пам'яті і процесора були в межах 90-100%. Це дослідження проводилося за допомогою команди top.

Також за весь час використання сервера були збої праці до цілої години.

Тому було прийнято рішення знайти потужніший і надійніший сервер.

В якості нового сервера був вибраний Root-сервер EX 5, наданий німецькою компанією Hetzner, сервери якої знаходяться поблизу міста Гамбург. Основні характеристики цього сервера наступні:

 - процесор: Intel® Core™ i7 - 920 Quad-Core;

 - ОЗП: 24 GB DDR3 RAM;

- жорсткий диск: 1.5 Тб;

- трафік необмежений;

- ціна 76 у. е.

Співвідношення ціна-якість німецького сервера набагато кращі за український. Також компанія Hetzner надає дуже зручну функцію автоматичного сповіщення про збої по смс і e-mail.

Протягом одного дня усі веб-додатки були перенесені на новий сервер і при тій же аудиторії трохи більше 100 000 чоловік завантаження оперативної пам'яті і процесора при перевірці командою top склало близько 1% (див. рисунок 3.1).

Рисунок 3.1 - Графік залежності завантаження сервера від кількості користувачів веб-додатків

Таким чином був зроблений висновок, що проект SportDiary на початковому етапі слід розмістити на сервері Hetzner. Оскільки сервер з легкістю обслуговує додатки для соціальних мереж з аудиторією 130 000, то й додаток SportDiary обслуговуватиметься ще простіше, оскільки запити до сервера в іграх соціальних мереж повинні відбуватися набагато частіше, ніж до спортивного щоденника.

В якості HTTP-сервера був встановлений nginx. Для обробки скриптів був використаний обробник PHP-FPM, що працює в режимі FastCGI.

РНР має вбудований зв'язок із багатьма системами баз даних, у тому числі і MySQL [2]. Це дуже зручна і ефективна технологія.

Нижче приведені основні директиви, які використовувалися при розробці сайту sportdiary.net, і які прописані в конфігураційному файлі nginx :

- fastcgi_intercept_errors on | off – визначає, чи передавати клієнтові відповіді FastCGI-сервера з кодом більше або рівним 400, або ж перенаправляти їх на обробку nginx'у за допомогою директиви error_page. У конфігураційному файлі встановлено значення on;

- fastcgi_ignore_client_abort on | off – визначає, чи закривати з'єднання з FastCGI-сервером у разі, якщо клієнт закрив з'єднання, не дочекавшись відповіді. Встановлено значення off;

- fastcgi_connect_timeout час – задає таймаут для встановлення з'єднання з FastCGI-сервером. Необхідно мати на увазі, що цей таймаут зазвичай не може перевищувати 75 секунд. Встановлений в значення 60;

- fastcgi_send_timeout час – задає таймаут при передачі запиту FastCGI-серверу. Таймаут встановлюється не на усю передачу запиту, а тільки між двома операціями запису. Якщо після закінчення цього часу FastCGI-сервер не прийме нових даних, з'єднання закривається. Встановлений в значення 180;

- fastcgi_read_timeout час – задає таймаут при читанні відповіді FastCGI-сервера. Таймаут встановлюється не на усю передачу відповіді, а тільки між двома операціями читання. Якщо після закінчення цього часу FastCGI -сервер нічого не передасть, з'єднання закривається. Встановлений в значення 180;

- fastcgi_buffer_size розмір – задає розмір буфера, в який читатиметься перша частина відповіді, що отримується від FastCGI-сервера. У цій частині відповіді знаходиться, як правило, невеликий заголовок відповіді. За умовчанням розмір буфера дорівнює розміру одного буфера в директиві fastcgi_buffers, проте його можна зробити менше. Встановлений в значення 128k;

- fastcgi_buffers число розмір – задає число і розмір буферів для одного з'єднання, в які читатиметься відповідь, що отримується від FastCGI -сервера. За умовчанням розмір одного буфера дорівнює розміру сторінки. Залежно від платформи це або 4K, або 8K. Встановлено значення 4 256k;

- fastcgi_busy_buffers_size розмір – обмежує сумарний розмір буферів, які можуть бути зайняті для відправки відповіді клієнтові, поки відповідь ще не прочитана цілком. Буфери, що залишилися, тим часом можуть використовуватися для читання відповіді і, при необхідності, буферизації частини відповіді в тимчасовий файл. За умовчанням розмір обмежений двома буферами, заданими директивами fastcgi_buffer_size і fastcgi_buffers. Встановлено значення 256k;

- fastcgi_temp_file_write_size розмір – обмежує розмір даних, що скидаються в тимчасовий файл за один раз, при включеній буферизації відповідей FastCGI-сервера в тимчасові файли. За умовчанням розмір обмежений двома буферами, заданими директивами fastcgi_buffer_size і fastcgi_buffers. Максимальний розмір тимчасового файлу задається директивою  fastcgi_max_temp_file_size [11]. Встановлено значення 256k.

Для обробки баз даних був встановлений MySQL-сервер.

Для підвищення продуктивності необхідно оптимізувати MySQL-сервер. Далі будуть описані різні настройки MySQL, які були протестовані.

Базові настройки:

- low-priority-updates ця опція знижує пріоритет операцій INSERT / UPDATE в порівнянні з SELECT. Актуально, якщо дані важливо швидше прочитати, чим швидше записати;

- skip-external-locking опція встановлена ​​за замовчуванням, починаючи з версії 4. Вказує MySQL-серверу не використовувати зовнішні блокування при роботі з базою. Зовнішні блокування необхідні в ситуаціях, коли кілька серверів працюють з одними і тими ж файлами даних, тобто мають однакову datadir, що на практиці не використовується;

- skip-name-resolve не визначати доменні імена для IP-адрес клієнтів, що підключаються. При цьому користувальницькі дозволу потрібно налаштовувати не на хости, а на IP-адреси (за винятком localhost). Якщо користувач з'єднується із сервером тільки з локальної машини, то особливого значення не має. Для зовнішніх з'єднань прискорить установку з'єднання;

- skip-networking не використовувати мережу, тобто взагалі не обробляти TCP / IP з'єднання. Спілкування з сервером при цьому буде відбуватися виключно через сокет. Рекомендується, якщо у немає ПО, яке використовує тільки TCP / IP для зв'язку з сервером.

Обмеження:

- bind-address інтерфейс, який буде прослуховувати сервер. З метою безпеки рекомендується встановити 127.0.0.1, якщо не використовуються зовнішні з'єднання з сервером;

- max_allowed_packet максимальний розмір даних, які можуть бути передані за один запит. Слід збільшити, якщо виникає помилка Packet too large;

- max_connections максимальна кількість паралельних з'єднань до сервера. Необхідно збільшити, якщо виникає проблема Too many connections;

- max_join_size забороняє SELECT оператори, які імовірно будуть аналізувати більш вказаного числа рядків або більше вказаного числа пошуків по диску. Використовується для захисту від поганих запитів, які намагаються рахувати мільйони рядків. Значення за умовчанням більше 4 мільярдів;

- max_sort_length вказує, скільки байтів з початку полів типу BLOB або TEXT використовувати при сортуванні. Значення за умовчанням 1024, якщо є імовірність некоректно спроектованих таблиць або запитів, то слід його зменшити.

Настройки потоків:

- thread_cache_size вказує число кешованих потоків. Після обробки запиту сервер не завершуватиме потік, а розмістить його в кеші, якщо число потоків, що знаходять в кеші менше, ніж вказане значення. Значення за умовчанням 0, необхідно збільшити його до 8 або відразу до 16. Якщо спостерігається ріст значення змінної стану Threads_Created, то слід ще збільшити thread_cache_size;

Кешування запитів:

- query_cache_limit максимальний розмір кешувального запиту;

- query_cache_min_res_unit мінімальний розмір збереженого в кеші блоку;

 - query_cache_size  розмір кеша. 0 відключає використання кеша. Для вибору оптимального значення необхідно спостерігати за змінною стану Qcache_lowmem_prunes і домогтися, щоб її значення збільшувалося незначно. Також потрібно пам'ятати, що надмірно великий кеш буде створювати непотрібне навантаження;

 - query_cache_type  (OFF, DEMAND, ON). OFF відключає кешування, DEMAND — кешування буде вироблятися тільки при наявності директиви SQL_CACHE в запиті, ON включає кешування;

- query_cache_wlock_invalidate  визначає, чи будуть дані братися з кеша, якщо таблиця, до яких вони відносяться, заблокована на читання.

 Кеш запитів можна представити як хеш-масив, ключами якого є запити, а значеннями результати запитів. Крім результатів, MySQL зберігає в кеші список таблиць, вибірка з яких закешірована. Якщо в будь-який з таблиць, вибірка з якої є в кеші, проіcходят зміни, то MySQL видаляє з кешу такі вибірки. Також MySQL не кешує запити, результати яких можуть змінитися.

При запуску MySQL виділяє блок пам'яті розміром в query_cache_size. При виконанні запиту, як тільки отримано перші рядки результату сервер починає кешувати їх: він виділяє в кеші блок пам'яті, рівний query_cache_min_res_unit, записує в нього результат вибірки. Якщо не вся вибірка помістилася в блок, то сервер виділяє наступний блок і так далі. У момент початку запису MySQL не знає про розмір отриманої вибірки, тому якщо записаний у кеш розмір вибірки більше, ніж query_cache_limit, то запис припиняється і зайняте місце звільняється, отже, якщо відомо наперед, що результат вибірки буде більшим, варто виконувати його з директивою SQL_NO_CACHE.

Таймінги:

- interactive_timeout час в секундах, протягом якого сервер очікує активності з боку інтерактивного з'єднання (використовує прапор CLIENT_INTERACTIVE), перш ніж закрити його;

- log_slow_queries вказує сервера логувати довгі («повільні») запити (виконуються довше long_query_time). Як значення передається повне ім'я файлу (наприклад / var / log / slow_queries);

- long_query_time якщо запит виконується довше зазначеного часу (в секундах), то він буде вважатися повільним;

- net_read_timeout час в секундах, протягом якого сервер буде очікувати отримання даних, перш ніж з'єднання буде перервано. Якщо сервер не обслуговує клієнтів з дуже повільними або нестабільними каналами, то 15 секунд буде достатньо;

- net_write_timeout час в секундах, протягом якого сервер буде очікувати відправку даних, перш ніж з'єднання буде перервано. Якщо сервер не обслуговує клієнтів з дуже повільними або нестабільними каналами, то 15 секунд буде достатньо;

- wait_timeout час в секундах, протягом якого сервер очікує активності сполуки, перш ніж перерве його. У загальному випадку 30 секунд достатньо.

Буфери:

- key_buffer_size розмір буфера, який виділяється під індекси і доступного всім потокам. Вельми важлива настройка, яка впливає на продуктивність. Значення за умовчанням 8 МБ, його однозначно варто збільшити. Рекомендується 15-30% від загального обсягу ОЗУ, однак немає сенсу встановлювати більше, ніж загальний розмір усіх MYI файлів. Необхідно спостерігати за змінними стану Key_reads і Key_read_requests, ставлення Key_reads / Key_read_requests повинно бути якомога менше (<0,01). Якщо це відношення велике, то розмір буфера варто збільшити;

- max_heap_table_size максимальний допустимий розмір таблиці, що зберігається в пам'яті (типу MEMORY). Значення за замовчуванням 16 МБ, якщо ви не використовуєте MEMORY таблиць, то встановіть це значення рівним tmp_table_size;

- myisam_sort_buffer_size розмір буфера, який виділяється MyISAM для сортування індексів при REPAIR TABLE або для створення індексів при CREATE INDEX, ALTER TABLE. Значення за умовчанням 8 МБ, його варто збільшити аж до 30-40% ОЗУ. Виграш в продуктивності відповідно буде тільки при виконанні вищезазначених запитів;

- net_buffer_length об'єм пам'яті, що виділяється для буфера з'єднання і для буфера результатів на кожен потік. Буфер з'єднання буде зазначеного розміру і буфер результатів буде такого ж розміру, тобто на кожен потік буде виділений подвійний розмір net_buffer_length. Вказане значення є початковим і при необхідності буфери будуть збільшуватися аж до max_allowed_packet. Розмір за замовчуванням 16 КБ. У разі обмеженої пам'яті або використання тільки невеликих запитів значення можна зменшити. У разі ж постійного використання великих запитів і достатнього обсягу пам'яті, значення варто збільшити до передбачуваного середнього розміру запиту;

- read_buffer_size кожен потік при послідовному скануванні таблиць виділяє зазначений обсяг пам'яті для кожної таблиці. Як показують тести, це значення не слід особливо збільшувати. Розмір за замовчуванням 128 КБ, спробуйте збільшити його до 256 КБ, а потім до 512 КБ і поспостерігайте за швидкістю виконання запитів типу SELECT COUNT (*) FROM table WHERE expr LIKE "a%"; на великих таблицях;

- read_rnd_buffer_size актуально для запитів з "ORDER BY", тобто для запитів, результат яких повинен бути відсортований і які звертаються до таблиці, що має індекси. Значення за замовчуванням 256 КБ, збільште його до 1 МБ або вище, якщо дозволяє пам'ять. Врахуйте, що вказане значення пам'яті також виділяється на кожний потік;

- sort_buffer_size кожен потік, що виробляє операції сортування (ORDER BY) або угруповання (GROUP BY), виділяє буфер вказаного розміру. Значення за замовчуванням 2 ​​МБ, якщо ви використовуєте зазначені типи запитів і якщо дозволяє пам'ять, то значення варто збільшити. Велике значення змінної стану Sort_merge_passes вказує на необхідність збільшення sort_buffer_size. Також варто перевірити швидкість виконання запитів виду SELECT * FROM table ORDER BY name DESC на великих таблицях, можливе збільшення буфера лише сповільнить роботу (в деяких тестах це так);

- table_cache (table_open_cache з версії 5.1.3) кількість кешованих відкритих таблиць для всіх потоків. Відкриття файлу таблиці може бути досить ресурсномісткою операцією, тому краще тримати відкриті таблиці в кеші. Слід врахувати, що кожен запис в цьому кеші використовує системний дескриптор, тому можливо доведеться збільшувати обмеження на кількість дескрипторів (ulimit). Значення за замовчуванням 64, його найкраще збільшити до загальної кількості таблиць, якщо їх кількість в допустимих рамках. Змінна стану Opened_tables дозволяє відстежувати число таблиць, відкритих в обхід кеша, бажано, щоб її значення було якомога нижче;

- tmp_table_size максимальний розмір пам'яті, виділеної для тимчасових таблиць, створюваних MySQL для своїх внутрішніх потреб. Це значення також обмежується змінної max_heap_table_size, тому в підсумку буде вибрано мінімальне значення з max_heap_table_size і tmp_table_size, а інші тимчасові таблиці будуть створюватися на диску. Значення за умовчанням залежить від системи, спробуйте встановити його рівним 32 МБ і поспостерігати за змінною стану Created_tmp_disk_tables, її значення повинно бути якомога менше.

Значення в конфігураційному файлі задаються в байтах, відповідно кілобайти і мегабайти потрібно переводити в байти.

InnoDB:

- innodb_additional_mem_pool_size розмір пам'яті, що виділяється InnoDB для зберігання різних внутрішніх структур. Якщо InnoDB буде недостатньо цієї пам'яті, то буде запитана пам'ять у ОС і записано попередження в лог помилок MySQL;

- innodb_buffer_pool_size розмір пам'яті, що виділяється InnoDB для зберігання і індексів і даних. Значення - чим більше, тим краще. Можна збільшувати аж до загального розміру всіх InnoDB таблиць або до 80% ОЗУ, в залежності від того, що менше;

- innodb_flush_log_at_trx_commit має три допустимих значення: 0, 1, 2. При значенні рівному 0, лог скидається на диск один раз в секунду, незалежно від відбуваються транзакцій. При значенні рівному 1, лог скидається на диск при кожній транзакції. При значенні рівному 2, лог пишеться при кожній транзакції, але не скидається на диск ніколи, залишаючи це на совісті ОС. За замовчуванням використовується 1, що є найнадійнішою налаштуванням, але не найшвидшою. У загальному випадку ви можете сміливо використовувати 2, дані можуть бути загублені лише в разі краху ОС і лише за декілька секунд (залежить від налаштувань ОС). 0 - найшвидший режим, але дані можуть бути загублені як при краху ОС, так і при краху самого сервера MySQL (втім дані лише за 1-2 секунди);

 - innodb_log_buffer_size  розмір буфера логу. Значення за замовчуванням 1 МБ, збільшувати його варто, якщо ви знаєте, що буде велика кількість транзакцій InnoDB або якщо значення змінної стану Innodb_log_waits зростає. Вам навряд чи доведеться збільшувати його вище 8 МБ;

- innodb_log_file_size  максимальний розмір одного лог-файлу. При досягненні цього розміру InnoDB буде створювати новий файл. Значення за замовчуванням 5 МБ, збільшення розміру поліпшить продуктивність, але збільшить час відновлення даних. Встановіть це значення в діапазоні 32 МБ - 512 МБ в залежності від розміру сервера (оцінивши його суб'єктивно) [9].

Нижче описані рекомендації до роботи з таблицями для досягнення більшої продуктивності :

1. По можливості усі поля декларувати як NOT NULL. Це зробить роботу з таблицями швидшою і збереже 1 біт на кожне таке поле.

2. Застосовувати значення за умовчанням (DEFAULT). При виклику запиту INSERT в таблицю записуватимуться тільки ті поля, значення яких відрізняються від DEFAULT.

3. Використовувати настільки малі типи INT, наскільки це можливо.

4. При використанні декількох послідовних INSERT запитів, краще усі дані вказати в одному INSERT, чим робити декілька INSERT [6].

Конфігураційний файл MySQL можна подивитися в доповненні Б.

В ході розробки та тестування проекту було поставлене питання використовувати G++ чи PHP. Виявилося, що швидкість роботи при використанні G++ на порядок швидше, але за умовою, що немає звертання до банку даних. Якщо йде звертання до банку даних, то час обробки однаковий.

Графік залежності кількості оброблюваних запитів в секунду від вибраної технології зображений на рисунку 3.2.

 Тестування проводилося за допомогою утиліти Apache Bench (ab). Такого роду тест не дає результату, наближеного до реальності, оскільки у реальному режимі користувачі звертаються до різних скриптів, а також до статичних об'єктів. Також ця утиліта не враховує ширини каналу. Але для порівняння двох технологій вона є прийнятною.

 При запиті до двох скриптів на G++ і PHP з однаковою функціональністю і без звернення до бази даних співвідношення кількості запитів до стрипту в

Рисунок 3.2 - Графік залежності кількості оброблюваних сервером запитів від вибраної технології

секунду можна виразити формулою:

G = 20*P,                                                         (4.1)

де G – кількість запитів до скрипта на G++, P – кількість запитів до скрипта PHP в секунду.

При запиті до двох скриптів з однаковою функціональністю, але із зверненням до бази даних, співвідношення кількості запитів можна виразити формулою:

G ≈ P                                                                 (4.2)

Таким чином залежність можна об'єднати в одну формулу:

     G = k * P       (4.3)

де G – кількість запитів до скрипта на G++, P – кількість запитів до скрипта на PHP, k = 1, якщо в скрипті є звернення до бази даних,  k = 20, якщо звернення до бази даних немає.

Оскільки в усіх скриптах, використовуваних в проекті SportDiary є звернення до бази даних, то дуже складно зробити вибір лише за критерієм швидкодії.

Проте за критерієм надійності вибір технології PHP є переважним. Якщо відбувається збій сервера, перезапустити програму G++ можна тільки вручну. PHP у свою чергу перезапускається автоматично. Тому для написання скриптів був вибрана саме технологія PHP.

Для забезпечення швидкого завантаження сторінки і зручності для користувачів, сторінка завантажується таким чином: спочатку завантажується статична html-сторінка, і тільки потім вона заповнюється даними за допомогою jQuery і PHP-скриптів.

Початковий код сторінок написаний на html5 і JavaScript з використанням бібліотеки jQuery. JavaScript є найбільш популярною скриптовою мовою [12].  Виходячи з попереднього досвіду програмування, він є найбільш зручним і ефективним.

 

3.2. Розробка соціальної мережі і забезпечення її взаємодії з мобільним додатком

 За своїмі функціональними можливостями соціальна мережа www.sportdiary.net практично ідентична з тією, яка вбудована в мобільне застосування. У неї є тільки дві додаткові функції:

1) коментування статистики користувачів;

2) вхід в соціальну мережу під логіном і паролем, виданим в мобільному додатку.

Інтерфейс соціальної мережі sportdiary.net зображений на рисунку 3.3.

 Для синхронізації мобільного застосування з сайтом були розроблені спеціальні API.

 Для того, щоб відправити якісь дані на сервер з мобільного додатку необхідно виконати POST-запит по вказаному посиланню і передати в тілі запиту необхідні дані. Для того, щоб отримати якісь дані від сервера, необхідно виконати GET-запит і отримати дані у відповідь у вигляді JSON-масиву.

Рисунок 3.3 - Інтерфейс соціальної мережі sportdiary.net

 

 Нижче наведений приклад GET-запиту перевірки наявності Інтернету :

 int num = rand();

           NSString * request = [NSString stringWithFormat:@"http://sportdiary.net/api/isinet.php?rand=%i", num];

           NSData* data = [NSData dataWithContentsOfURL:

                                               [NSURL URLWithString : request]];

 Усі URL-запити передаються на сервер nginx, який у свою чергу виконує усі PHP-скрипты в режимі FastCGI. Виконаний скрипт передає вихідні дані в сокет, з якого прочитується JSON-массив, що потрапляє в об'єкт NSData* data.

Вміст скрипта isinet.php приведене нижче:

 <?

 echo '{"status": "ok" }';

 ?>

Таким чином, якщо цей скрипт викликався, то у будь-якому випадку додатку вдалося з'єднатися з сервером, і Інтернет є. Інакше об'єкт отримає невизначені дані.

Аналогічні API-функции розроблені для наступних дій:

- перевірка Інтернету;

- отримання логіна і пароля;

- видалення або додавання вправи;

- зміна фотографії;

- зміна імені;

- синхронізація статистичних даних;

- завантаження користувачів;

- підписка на користувачів;

- завантаження новин;

- пошук користувачів;

- завантаження статистики користувачів.

Робота усіх скриптів була протестована. Усі дані синхронізуються справно і абсолютно аналогічні для мобільного застосування і соціальної мережі. На рисунках 3.4 і 3.5 зображений інтерфейс вкладки "Користувачі" мобільного застосування і сайту відповідно.

Рисунок 3.4 - Інтерфейс вкладки "People" в мобільному додатку SportDiary

 Як видно з приведених рисунків, дані синхронізовані коректно і є однаковими.

Рисунок 3.5 - Інтерфейс вкладки "People" на сайті sportdiary.net

Аналогічно синхронізуються статистичні дані по кожному користувачеві. Дані з локальної бази SQLite мобільного додатка SportDiary відправляються на сервер за допомогою POST-запиту і переписуються в базу даних SQL на сервері. Структура бази даних за статистикою аналогічна тій, яка була описана в мобільному застосуванні. Відмінністю є таблиця користувачів, в якій зберігаються наступні дані, :

- ім'я користувача;

- id користувача;

- логін користувача;

- пароль користувача.

Також в базі даних зберігаються деякі дані для взаємодії з іншими соціальними мережами, але ці дані у рамках магістерської роботи неважливі.

При перегляді статистики на сайті статистичні дані підвантажуються з бази даних на сервері і відображуються на екрані за допомогою вільної бібліотеки jQuery jqplot. Приклад графіку статистики зображений на рисунку 3.6.

  Рисунок 3.6 - Графік статистики на сайті sportdiary.net

3.3. Тестування роботи проекту на реальних користувачах

Заздалегідь була проведена зразкова оцінка можливостей сервера за допомогою утиліти Apache Bench.

Кількість звернень до різних скриптів в секунду варіювалася від 1 500 до 17 000 запитів за секунду. У такі пікові моменти мобільне застосування затримувалося приблизно на 2-3 секунди під час запитів до сервера. Проте неможливо зробити які-небудь точні висновки з отриманих даних. Не можна визначити, якою буде реальна максимальна кількість запитів користувачів до сервера в секунду.

По-перше, запити поступають до різних скриптів і статичних файлів з різною частотою. По-друге, невідомо, якою за розміром буде відповідь скрипта – 1 кілобайт або 1 мегабайт. Утиліта ab ніяк не враховує ширину каналу сервера (яка дорівнює 100 мбит).

Наочнішою є оцінка відвідувань реальних користувачів. Як згадувалося раніше, сервер вже обслуговує стотридцятитисячну аудиторію користувачів. І тому дуже зручно оцінити роботу спортивного щоденника в зв'язці з додатками для соціальних мереж.

Оскільки сервер обслуговуватиме одночасно соціальні додатки і додаток SportDiary, можна буде зробити приблизну оцінку можливостей сервера.

Аудиторія трьох додатків складає 130 000 чоловік. У пікові моменти за день додаток відвідували 15 000 унікальних користувачів і максимальне завантаження складало 200 запитів в секунду. При такому навантаженні на ресурси сервер був завантажений усього на 1%. З цього можна зробити висновок, що практично із стовідсотковою вірогідністю сервер витримає навантаження набагато більше, ніж 1000 запитів за секунду, якщо це дозволить ширина каналу. Оскільки дуже рідко один запит вимагає більше ніж 1 кілобайт (зазвичай значно менше), то сервер можна вважати високонавантаженим.

Таким чином проект SportDiary є високонавантаженим веб-сервісом.

Для поширення додатка по всьому світу слід прагнути, щоб він підтримував багато мов [3]. Тому надалі планується його переклад на німецьку, французьку, російську, італійську та інші мови.

Далі буде розглянута публікація додатка в офіційному магазині App Store і його тестування на реальній аудиторії.

Для публікації додатків, як платних, так і безкоштовних, необхідно реєструватися розробником на офіційному магазині Apple. Для цього треба заповнити бланк заяви з вказівкою своїх контактних даних і сплатити щорічний внесок  99 у. о.

Для того, щоб додаток розмістили в офіційному магазині, він у свою чергу повинен відповідати ряду вимог до змісту, а також справно працювати і не піддаватися критичним помилкам. Раніше було наведено тестування додатка на помилки і втрати пам'яті, яке підтвердило правильність виконання програми.

Для розміщення в офіційному магазині спочатку необхідно скомпілювати проект з налаштуваннями розробника. Головні налаштування розробника приведені на рисунку 3.7.

Рисунок 3.7 - Основні налаштування розробника проекту SportDiary в середовищі розробки XCode

 

Далі необхідно відкомпілювати проект і створити архів для подальшого розміщення в App Store. Сховище архівів в середовищі розробки XCode з проектом SportDiary приведене на риснуке 3.8.

 Далі необхідно пройти перевірку програмного продукту (Validate). Якщо перевірка пройшла успішно, додаток можна розміщувати в офіційному магазині (Distribute). 

Після цього додаток з'являється в профілі розробника на сайті itunesconnect.apple.com. Перед фінальною публікацією додатка треба скласти його опис (бажано на декількох мовах), зробити від одного до п'яти

Рисунок 3.8 - Сховище архівів в середовищі розробки XCode

скриншотів, а також виконати деякі налаштування. Інформація про проект в профілі розробника Itunesconnect зображена на рисунку 3.9.

Рисунок 3.9 - Інформація про проект SportDiary в профілі розробника Itunesconnect

Коли усі налаштування зконфігуровані, проект можна відправляти в чергу публікації. Проект SportDiary був розміщений в офіційному магазині приблизно через 2 тижні після відправлення заявки.

Менш ніж за 3 місяці проект набрав 300 користувачів. Статистика завантажень додатка SportDiary приведена на рисунку 3.10.

Рисунок 3.10 - Статистика завантажень додатка SportDiary

За весь час роботи додатка у користувачів не було жодного збою в роботі додатка. Додаток працює швидко і завантаження даних з сервера триває не більше за одну секунду. З урахуванням того, що сервер обслуговує ще 130 000 користувачів додатків для соціальних мереж, завантаження сервера складає біля одного відсотка.

4. РОЗРОБКА ВЕБ-СЕРВІСУ ПРИЙОМУ ЗАМОВЛЕНЬ ДЛЯ СЛУЖБ ТАКСІ

На основі налагодженого сервера і використаних технологій була розроблена система автоматичного прийому замовлень для служб таксі.

Система виконує функцію автоматичного пошуку найближчого таксиста. Також в базу даних передбачений збір різних статистичних даних, які можуть бути взяті до уваги для можливого коригування роботи таксистів.

В процесі дослідження і спілкування з кваліфікованими працівниками в цій області було прийнято рішення розробити напівавтоматичну систему прийому замовлень, тобто для того, щоб викликати машину, клієнт повинен подзвонити з мобільного або стаціонарного телефону. Такий принцип роботи вибраний з міркувань надійності. Якщо зробити повністю автоматичну систему замовлень (клієнт замовляє машину з веб-сайту), то є велика вірогідність того, що багато замовлень будуть помилковими.

Другим чинником є те, що замовлення по телефону викликають у людини більше довіри.

Інтерфейс розробленого сайту для прийому замовлень зображений на рисунку 4.1.

Цей інтерфейс спочатку створений для диспетчера, проте за бажанням замовника його легко можна модифікувати різними способами і зробити сторінку для клієнта, начальника і т. п.

У лівому верхньому кутку екрану знаходиться область введення даних, в якій знаходиться три поля. У перші два поля вводяться адреса виклику і номер клієнта. У третє поле вводиться адреса призначення.

У нижній частині екрану знаходиться карта міста. Усе що пов'язане з локацією, пошуком вільних машин і відміткою таксистів на карті, реалізовано з використанням Maps Yandex API.

Оскільки цей проект на момент написання магістерської роботи знаходиться в Demo-режимі, то деякі функції в ньому відсутні. Це зроблено  із міркувань того, що для кожного замовника проект певним чином модифікуватиметься і немає сенсу створювати повністю функціональний програмний продукт.

Рисунок 4.1 - Інтерфейс сайту прийому замовлень для служб таксі

На карті в якості прикладу відмічено 5 вільних таксистів в різних частинах міста. На даний момент таксисти не можуть відзначатися самі, оскільки проект ще не введений в експлуатацію. При необхідності відмітку таксистів можна виконати різними способами:

- за допомогою мобільного додатку;

- за допомогою смс-повідомлення;

- повідомленням по рації.

При натисненні кнопки "Замовити" прораховуються маршрути усіх таксистів до місця замовлення, і вибирається найближчий таксист. Також диспетчер може подивитися інформацію про усіх таксистів. На рисунку 4.2 зображений процес вибору найближчого таксиста.

 Рисунок 4.2 - Вибір найближчого до адреси замовлення таксиста

Після вибору таксиста формується 2 смс-повідомлення: одне – таксистові, друге – клієнтові. Клієнт може подивитись номер таксиста, марку автомобіля, час, через який прибуде таксист і вартість проїзду. Таксист отримує номер телефону клієнта і адресу виклику.

На даний момент ці смс-повідомлення лише показові, але в реальному проекті досить просто додати смс-розсилку. Існує безліч сервісів, що надають цю послугу, включаючи офіційних операторів мобільного зв'язку.

В якості критерію вибору найближчої машини можна визначити або мінімальну відстань, або мінімальний час прибуття з урахуванням пробок. У цьому прототипі критерієм вибору є мінімальна відстань.

Для більшої наочності на карті показані маршрути усіх таксистів до місця замовлення (див. рисунок 4.3).

Рисунок 4.3 - Маршрути усіх таксистів до місця виклику

У статистичну базу даних заноситься інформація по кожному замовленню: номер таксиста, довжина маршруту до точки виклику, довжина маршруту поїздки, ціна поїздки, час до точки виклику, передбачуваний час поїздки. За бажанням замовника дані можна модифікувати, а також надати статистику у вигляді графіку, діаграма і т. п.

Таким чином система прийому замовлень для виклику таксі працює в демонстраційному режимі. До моменту введення в експлуатацію планується додати необхідні деталі за бажанням замовника, а також реальну відправку смс-повідомлень і реальну відмітку таксистів на карті міста.

Переваги системи прийому замовлень DoExclusiveTaxi наступні:

- прискорення процесу роботи диспетчерів;

- мала вірогідність помилкових замовлень;

- автоматичний прорахунок маршруту, а отже економія бензину і прискорення часу поїздки;

- зв'язок таксиста і клієнта за допомогою смс.

Недоліком є випадок, якщо клієнтові необхідно їхати в декілька точок. В цьому випадку систему треба модифікувати, інакше не можна прорахувати приблизну ціну поїздки.

5. ОХОРОНА ПРАЦІ ТА БЕЗПЕКА В НАДЗВИЧАЙНИХ СИТУАЦІЯХ

5.1. Аналіз умов праці відділу розробки web-проектів та програмного забезпечення для смартфонів

Я проходив практику у відділі розробки web-проектів і програмного забезпечення для смартфонів підприємства "DoExclusive", яке займається розробкою високонавантажених Інтернет-сервісів.

Відділ розробки web-проектів і програмного забезпечення для смартфонів знаходиться на першому поверсі дев'ятиповерхового будинку. Площа приміщення складає 30 м2.

Відділ виконує наступні види робіт :

- аналіз існуючого програмного забезпечення для смартфонів;

- розробка нового програмного забезпечення для смартфонів;

- розробка сайтів;

- розробка додатків для соціальних мереж.

Усі працівники на підприємстві користувалися розробленим додатком SportDiary. SportDiary заохочує заняття спортом і рухливий спосіб життя, що позитивно позначається на здоров'ї і тонусі  працівників.

Діяльність працівників відділу напряму пов’язана з роботою на комп’ютерному обладнанні (ноутбук або стаціонарний комп’ютер) та  використання мобільних телефонів.

До основних шкідливих та небезпечних факторів виробничого середовища, пов’язаних з роботою на персональному комп’ютері відповідно ГОСТ 12.0.003-74. ССБТ. «Опасные и вредные производственные факторы» належать:

- напруга зорових органів;

- перевтома;

- значне навантаження на кисті рук та пальців;

- тривале знаходження в сидячій позі, що викликає застійні явища в організмі;

- випромінювання різного виду (рентгенівське, електромагнітне, інфрачервоне, статистичні поля);

- механічні шуми, пов’язані з роботою кулера, дискового приводу, оргтехніки;

- іонізація повітря;

- виділення в повітря робочого приміщення різних хімічних речовин.

Мобільний телефон є також джерелом електромагнітного випромінювання, на яке організм певним чином реагує. Наукові досліди підтверджують негативний вплив таких пристроїв на мозок. Але вплив мобільного апарату на організм користувача і досі не є достатньо вивченим.

До психологічно шкідливих факторів, які впливають на людину при роботі з комп’ютером можна віднести розумову напругу та нервово-емоційне перевантаження, які виникають внаслідок підвищеної концентрації уваги.

Усі ці фактори негативно впливають на здоров’я працівників відділу програмного забезпечення та сприяють виникненню професійних захворювань:

- комп’ютерний зоровий синдром;

- радіохвильова хвороба;

- синдром висихання рогівки ока;

- кистьовий тунельний синдром;

- захворювання шкіри;

- захворювання кишкового тракту;

- серцево-судинні захворювання;

- стрес.

Негативними наслідками регулярного використання мобільного телефону є ослаблення пам’яті, часті головні болі, зниження уваги, напруга в барабанних перетинках, порушення сну, дратівливість.

5.2. Заходи щодо поліпшення умов праці

Для зменшення  шкідливого впливу негативних факторів виробничого середовища на працівників відділу програмного забезпечення необхідно вжити заходів щодо поліпшення їх умов праці.

Кімната, у якій розташоване робоче місце з ПК повинна мати природне освітлення, яке здійснюється через світлові прорізи, орієнтовані переважно на північ чи північний схід. Віконні прорізи такого приміщення мають бути обладнані регульованими пристроями (жалюзі, завіски, зовнішні козирки). Також приміщення має бути обладнане системою опалення, кондиціонування повітря  для забезпечення оптимальних показників мікроклімату. Загальний контур заземлення будівлі повинен бути виведений через розетку на кожне робоче місце з ПК.

 Забороняється для оздоблення інтер'єру приміщень з ПК застосовувати полімерні матеріали (деревинно-стружкові плити, шпалери, що миються, рулонні синтетичні матеріали, шаруватий паперовий пластик тощо), що виділяють у повітря шкідливі хімічні речовини.

У приміщеннях, де розташовано монітори, потрібно виконувати заходи щодо боротьби зі статичним полем. Найбільш простим способом відповідно до рекомендацій є підтримка відносної вологості повітря на рівні 50-60% за допомогою кондиціювання [13].

Площа на одне робоче місце з ПК відповідно «СНиП II-2, 09.02-88. Производственные здания промышленных предприятий. Нормы проектирования.» має становити не менше ніж 6,0 м2, а об'єм не менше ніж 20,0 м3.

Відстань від робочого місця з ПК до стіни з вікном повинна становити не менше ніж 1,5 м; від інших стін – на відстані 1 м, а відстань між столами – 1,5 м [14].

Кімната має природне й штучне освітлення згідно з ДБН В.2.5-28-2006 «Природне і штучне освітлення». Віконні прорізи мають однобічне розміщення й орієнтовані на захід. Загальне штучне освітлення виконане стельовими світильниками з люмінесцентними лампами типу ЛБ [15].

Конструкція робочого місця користувача ПК згідно з «ГОСТ 12.2.032-78, ССБТ. Рабочее место при выполнении работ сидя. Общие эргономические требования», «ГОСТ 12.2.064-81, ССБТ. Органы управления производственным оборудованием. Общие требования безопасности» має забезпечити підтримання оптимальної робочої пози: ступні ніг – на підлозі або підставці для ніг; стегна – в горизонтальній площині; передпліччя – вертикально; лікті – вертикально під кутом 90 град. До вертикальної площини; нахил голови -  15-20 град. Відносно вертикальної площини.

Для зменшення  шкідливого впливу негативних факторів виробничого середовища робоче місце з ПК повинно відповідати наступним гігієнічним вимогам:

  •  екран та клавіатура повинні розташовуватись на оптимальній відстані від очей користувача, що становить 800...900 мм;
  •  висота робочої поверхні робочого столу має регулюватися в межах 800 мм;
  •  робочий стіл повинен мати простір для ніг заввишки не менше ніж 600 мм, завширшки не менше ніж 500 мм, завглибшки (на рівні колін) не менше ніж 450 мм, на рівні простягнутої ноги - ніж 650 мм;
  •  висота поверхні сидіння має регулюватися в межах 400...500 мм, висота спинки стільця має становити (300 ± 20) мм, ширина - не менше ніж 380 мм;
  •  для зниження статичного напруження м'язів верхніх кінцівок слід використовувати стаціонарні або змінні підлокітники завдовжки не менше ніж 250 мм, завширшки 50...70 мм.

Для зменшення негативного впливу мобільного апарату необхідно:

  •  скоротити до мінімуму час розмови по телефону;
  •  при розмові знімати окуляри з металевою оправою, бо наявність такої оправи, що грає роль вторинного випромінювача, може привести до збільшення інтенсивності електромагнітного поля, що падає на певні ділянки користувача, порівняно зі стандартною ситуацією;
  •  обирати мобільний телефон з мінімальним значенням SAR (Specific Absorbtion Rate). SAR – одиниця виміру питомої величини поглинання випромінювання організмом людини; максимальна потужність випромінювання телефону. Максимальне значення SAR у Європі складає 2 Вт/кг;

Розташування робочих місць з ПК у відділі розробки веб-додатків на рисунку 5.1.                    

Рис. 5.1 – Облаштованість робочих місць із ПК відділу розробки веб-додатків 

     

Умовні позначення:

1 – кондиціонер;

2 – вікно;

3 – стіл з комп’ютером;

4 – принтер;

5 – двері;

6 – робоче місце.

5.3. Розрахунок кондиціювання повітря

Мікроклімат впливає на самопочуття людини, його працездатність і протікання фізіологічних процесів, від яких залежить підтримка постійності температури тіла. Теплові дії на організм можуть з’явитися причиною швидкого стомлення, зниження працездатності, ослаблення, опірності організму до шкідливих дій, різних захворювань.

Кондиціонування повітря – це створення та автоматична підтримка в приміщеннях постійних чи визначених метеорологічних умов, що змінюються по програмі, найбільш сприятливих для людей, що працюють чи необхідних для нормального протікання технологічного процесу.

При виділенні тепла у виробничому приміщенні визначають необмежений повітрообмін виходячи з рівності виділеного тепла в помешканні та виділення вентиляцією. Згідно з ДСН 3.3.6.042-99 «Санітарні норми мікроклімату виробничих приміщень» у приміщеннях з ПК параметри мікроклімату повинні бути такими: температура навколишнього середовища повинна бути в межах 25-22°С, а швидкість руху повітря – 0,1 – 0,2 м/с.

У приміщенні відділу програмного забезпечення є джерела тепловиділення, тому необхідно визначити необхідні умови його вентилювання.

Повітрообмін по теплу визначаємо по формулі:

 ізб

                                   L =                                      , м3/год.                       (4.1)          

 cpn ∙ (tвид -tпр)

де ізб – надлишкова кількість тепла, тобто різниця між його приходом і відходом, ккал/год;

            с - теплообміність повітря (0,237 ккал/кг °С);

      pn - обсягова вага повітря (1,226 кг/м3);

     tпр - температура приточного повітря (20°С).

     tвид - температура витяжного повітря (30°С).

        Розрахуємо надлишкове надходження тепла за формулою:

                            ,                                  (4.2)

      де Qуст - виділення тепла від устаткування;

Qпер - виділення тепла робітниками;

Qосв - надходження тепла від електричного освітлення;

Qср - надходження тепла від сонячної радіації через вікна.

Визначимо виділення тепла від устаткування за формулою:

,                                  (4.3)

де Р - сумарна потужність устаткування, кВт/год;

Ка - коефіцієнт установленої потужності (0,95);

Кб - коефіцієнт одночасної роботи (1,0).

ккал/год.

Розрахуємо:

ккал/год.

Визначимо виділення тепла від обслуговуючого персоналу за допомогою формули:

                                                                                   (4.4)

де n - кількість працюючих;

g - кількість тепла, що виділяє один працівник за годину згідно ДСН 3.3.6.042-99 Санітарні норми мікроклімату виробничих приміщень (90-120 ккал/год.)

Розрахуємо:

ккал/год.

Визначимо надходження тепла від електричного освітлення за формулою:

                                                                                   (4.5)

де Ем - нормована освітленість для цієї зорової роботи приймаємо відновідно СНиП II 4-79. «Строительные нормы и правила. Нормы проектирования. Искусственное и естественное освещение.рівним» 400 – 500 лк;

g1 - питоме тепловиділення на 1 м2 підлоги при 1 лк освітленості (для люмінесцентних ламп - 0,05 ккал/год.).

S - площа приміщення, м2.

Розрахуємо:

ккал/год.

Визначимо надходження тепла від сонячної радіації через вікна по формулі:

                               ,                                             (4.6)

де F - площа віконних прорізів (1,55 м2).

g2 - кількість тепла, що надходить через 1 м2 віконного прорізу

(65 ккал/год.).

Косл - коефіцієнт ослаблення, приймаємо - 0,4.

Розрахуємо:

Qср=1,55∙65∙0,4 = 40,3 ккал/год.

Визначимо кількість надлишкового тепла:

Qнад= 1797,4 + 360 + 500 + 40,3 = 2697,7 ккал/год.

Визначимо витрати повітря в приміщенні:

Існуюча в наявності система кондиціонування і вентилювання має продуктивність 2000 м3/год, що задовольняє необхідним нормативам.

Параметри мікроклімату на робочих місцях регламентуються ДСН 3.3.6.042-99 «Санітарні норми мікроклімату виробничих приміщень». Відповідно доданих санітарних норм температура повітря, швидкість руху повітря і відносна вологість у холодні періоди року повинна складати 21-23 градуса по Цельсію, 0,1 метра в секунду і 40-60 % відповідно. У теплі періоди року температура повітря повинна складати 22-24 градусів Цельсія, рухливість повітря 0,2 метра за секунду, вологість 40-60 %. Вище зазначені норми цілком відповідають фактичним даним приміщення відділу програмного забезпечення.

5.4. Розрахунок системи загального рівномірного освітлення з лампами розжарювання для приміщення, в якому використовуються зорові роботи

Одним з основних питань охорони праці є організація раціонального освітлення виробничих приміщень робочих місць.

Для освітлення приміщення, у якому працюють фахівці, використається змішане освітлення, тобто сполучення природного й штучного освітлення. Природне освітлення здійснюється через вікно. Штучне освітлення використовується при недостатньому природному освітленні й здійснюється за допомогою двох систем: загального й місцевого освітлення.

Розрахунок системи освітлення виробляється методом коефіцієнта використання світлового потоку, що виражається відношенням світлового потоку, що падає на розрахункову поверхню, до сумарного потоку всіх ламп.    

Його величина залежить від характеристик світильника, розмірів приміщення, фарбування стін і стелі, яка характеризується коефіцієнтами відбиття стін і стелі.

Світловий потік при загальному освітленні визначається за формулою:

Фзагн·S·k·z/η,                                              (4.7)

де E – нормована освітленість робочого місця по нормі, лк (Е=400 лк);

S – площа приміщення, м2;

k – коефіцієнт запасу, що враховує зношування й забруднення світильників (k = 1,3);

Z – коефіцієнт, що враховує нерівномірність висвітлення (Z = 1,1);

h – коефіцієнт використовування світлового потоку, що вибирається з таблиць залежно від типу світильника, розмірів приміщення, коефіцієнта відбиття стін і стелі приміщення.

Оберемо з таблиці коефіцієнт використання світлового потоку за наступним даними:

  •  коефіцієнт відбиття для стелі приймемо rп = 60%;
  •  коефіцієнт відбиття стін rз = 30%;

i = , (4.8)

де A і B – сторони приміщення, м;

Hc – розрахункова висота підвісу світильника, м.

Розрахункова висота визначається за формулою:

Нс=H-hc-hр,                            (4.9)

де H – висота приміщення, м;

hс – відстань світильників від перекриття (hс=0,1 м);

hр – висота робочої поверхні над підлогою (hр=0,8 м).

Таким чином, розрахункова висота дорівнює:

Hc=3,0-0,1-0,8=2,1 м.

Підставляючи значення у формулу (5.7), одержимо:

i = .

Тоді, визначаємо значення коефіцієнта використання світлового потоку h  = 0,45.

Загальний світловий потік дорівнює:

=  лм.

Найбільш прийнятними для приміщення є люмінесцентні лампи ЛБ (білого світла), потужністю 80 Вт які мають потік  Ф1=4320 лм. Кількість ламп дорівнює:

                                          N = ; шт.                                           (4.10)

     Підставляючи значення, отримуємо:

N = = 8 шт

Якщо у світильники встановити по 2 лампи 80 Вт, з потоком 4320 лм, то потрібно розташувати 4 світильники на відстані 2 м до центру кімнати.

5.5. Пожежна безпека

Найважливішою умовою роботи будь-якого підприємства є дотримання правил пожежної охорони.

У приміщенні відділу програмного забезпечення основні міри для забезпечення пожежної безпеки визначає Інструкція про заходи пожежної безпеки для службових приміщень. Вона є обов’язковою для виконання всіма співробітниками.

В інструкції про засоби пожежної безпеки для службових приміщень забороняється:

- улаштовувати тимчасові електромережі, застосовувати саморобні плавкі вставки в запобіжниках, прокладати електричні проводи безпосередньо по пальній основі, експлуатувати світильники зі знятими ковпаками (розсіювачами), використовувати саморобні подовжувачі, що не відповідають вимогам Правил пристрою електроустановок;

- пристосовувати вимикачі, штепсельні розетки для підвішування одягу й інших предметів, обгортати електролампи і світильники, заклеювати ділянки електромережі пальною тканиною, папером;

- використовувати побутові електрокип'ятильники, чайники тощо без непальних підставок, залишати без нагляду включеними в електромережу кондиціонери, комп'ютери, рахункові і друкарські машинки і т.п.;

- захаращувати підступи до засобів пожежогасіння, використовувати пожежні крани, рукави і пожежний інвентар не по призначенню, зберігати документи, різні матеріали, предмети й інвентар у шафах (нішах) інженерних комунікацій;

- курити (крім спеціально відведених для  цього адміністрацією місць, позначених написом «Місце для паління» і забезпечених урною чи попільницею з непального матеріал), проводити зварювальні й інші вогневі роботи без оформлення відповідного дозволу, застосовувати легкозаймисті рідини.

По закінченню роботи необхідно:

- оглянути приміщення, переконатися у відсутності порушень, що можуть спричинити пожежу;

- відключити освітлення, електроживлення приладів і устаткування.

Електромережі, електроприлади і апаратура повинні експлуатуватися тільки у справному стані. У випадку виявлення ушкоджень електромереж, вимикачів, розеток ті інших електровиробів варто негайно відключити їх і вжити необхідних заходів до приведення їх в пожаробезпечний стан.

5.6. Розрахунок шуму

Відповідно до ДСН 3.3.6.037-99 „Санітарні норми виробничого шуму, ультразвуку та інфразвуку” , в приміщеннях з робочим місцем на ПК рівень звуку не повинен перевищувати 50 дБ.

Необхідно розрахувати наявний шум в приміщенні та в разі перевищення рекомендованого санітарними нормами рівня запропонувати шляхи зниження шуму. Для розрахунку будемо користуватися інформацією джерел.

Джерелами шуму у приміщенні є:

- 3 комп’ютери з рівнем звукового тиску L1 = 42 дБ;

- принтер з рівнем звукового тиску L2 = 60 дБ;

- кондиціонер з рівнем звукового тиску L3 = 65 дБ.

Розрахункову точку представлено на рисунку 4.1.

r1=2,2 м;  r2=2,8 м; r3 =3,5 м.

Якщо розрахункова точка знаходиться в приміщенні з декількома джерелами шуму, то визначення очікуваних рівнів звукового тиску знаходиться за наступною формулою:

                                                         (4.11)

де Lp - рівень звукової потужності джерела шуму, дБ;

Ф - фактор спрямованості джерела шуму (для джерела з рівномірним випромінюванням Ф=1);

r - відстань від геометричного центру джерела до розрахункової точки, м;

В - постійна приміщення (визначається по графіку залежності від об'єму приміщення), м2;

i - сума рівнів звукової потужності, розраховується за формулою:

                                               i = 100,1 Lpі                                                                     (4.12)

- рівень звукової потужності i - того джерела, дБ;

 - кількість джерел, що знаходяться в зоні прямої видимості з розрахункової точки;

п  -  загальна кількість джерел в приміщенні.

Визначимо Lp - рівень звукової потужності джерела шуму.

Сумарний рівень звукової потужності джерел з однаковим шумом (комп’ютери):

                   дБ                        (4.13)

                                   

Сумарний рівень шуму джерел з різним шумом (комп’ютер + принтер):

                           дБ                            (4.14)

                                           

- добавка

Сумарний рівень шуму джерел з різним шумом (комп’ютери + принтер + кондиціонер):

дБ

- добавка

Сума рівнів звукової потужності:

дБ

Очікуваний рівень звукового тиску:

дБ

Очікуваний рівень звукового тиску перевищує нормальний за санітарними нормами.

Необхідне зниження рівнів звукового тиску L визначається по формулі:

                                          L= L-Lдоп                                (4.15)                           

де L - виміряний рівень звукового тиску на робочих місцях підприємства, визначений в розрахункових точках (очікуваний рівень звукового тиску);

Lдоп - припустимий по нормах рівень звукового тиску, дБ.

L=62,8-50=12,8 дБ

Розрахуємо величину зниження рівня звукового тиску за рахунок акустичної обробки, зокрема при установці звукопоглинального облицювання.

Рівень звуку після застосування звуковбирного облицювання розраховують по формулі:

                                                                                       (4.16)                                            

де В – постійна приміщення, м2;

В1 – постійна приміщення після акустичної обробки, м2 .

                                                                                        (4.17)                                    

де  А1 – еквівалентна площа звукопоглинання поверхнями, не зайнятими звуковбирним облицюванням.

ΔА – додаткове звукопоглинання, внесене звуковбирним облицюванням;

- середній коефіцієнт звукопоглинання акустично обробленого приміщення.

                                                 A1=α(S-Sобл)                                       (4.18)

                                                                                     (4.20)

                                                    ΔА=αоблSобл                                                              (4.21)

                                                     α=B/(B+S)                                        (4.22)

де S – загальна площа всіх поверхонь приміщення, м2;

Sобл – площа звуковбирного облицювання, м2;

α - середній коефіцієнт звукопоглинання внутрішніх поверхонь приміщення площею S до установки облицювання площею Sобл

αобл – ревербераційний коефіцієнт звуковбирного облицювання.

Для акустичної обробки приміщення виберемо плити марки ПА/О, мінераловатні акустичні розмір 500Х500 (ТУ 21-24-60-74), обл =0,98.

S=(6*6*2+6*3*4)=144 м2

Sобл=6*3*4-2-5=65 м2

α = 8/(8+144)=8/152=0,0526

A1=0,0526(144-65)=4,15 м2

ΔА= 65*0,98=63,7 м2

= (4,15+63,7)/144=0,47

м2

дБ.

Таким чином, акустична обробка приміщення дасть змогу знизити рівень шуму в приміщенні на 11,8 дБ.

5.7. Безпека при надзвичайних ситуаціях на підприємстві

Об’єктом розгляду на предмет визначення надзвичайних небезпек та їх наслідків є відділ для створення програмного забезпечення. Однією із вірогідних загроз може бути раптове виникнення пожару внаслідок короткого замикання в електромережах або розрядів статистичної електрики, що може привести до пошкодження і руйнування будівлі, устаткування, комунікацій, виділення токсичних продуктів горіння.

Тому для відділу розроблено оперативний план гасіння пожежі, який визначає порядок дії персоналу при пожежі, порядок її гасіння в електроустановках, взаїмодію з власним складом пожежних підрозділів, а також застосування схем и засобів пожежогасіння з урахуванням заходів безпеки.

Будівля відділу повинна бути обладнана мережею протипожежного водо забезпечення, установками виявлення та гасіння пожеж відповідно вимогам нормативно-технічних документів. Кожний працівник повинен чітко знати та виконувати вимоги ППБ та протиаварійний режим на об’єкті, уміти користуватися наявними вогнегасниками, іншими первинними засобами пожежогасіння і знати місце їхнього перебування.

Меблі й устаткування повинні розміщатися таким чином, щоб забезпечувався вільний евакуаційний прохід до дверей виходу з приміщення (шириною не менше 1 м). Евакуаційні шляхи і виходи необхідно постійно держати вільними, нічим не захаращувати. Засоби протипожежного захисту в приміщеннях потрібні триматися у справному стані.

У випадку виявлення пожежі слід:

- негайно повідомити державну пожежну охорону за телефоном «101», вказати при цьому адресу, кількість поверхів, місце виникнення пожежі, наявність людей, своє прізвище;

- повідомити про пожежу керівництву, а в нічний час черговому охоронцю;

У разі можливості почати гасіння пожежі наявними засобами, організувати зустріч пожежних підрозділів.

При виникненні пожежі у початковій стадії його розвитку випромінюється тепло, токсичні продукти згоряння, імовірні руйнування будівельних споруд. Тому слід як можна швидше провести евакуацію людей із палаючої будівлі. Показником ефективності евакуації є час, протягом якого працівники можуть при потрібності залишити окремі приміщення і будівлю в цілому. Безпека евакуації досягається тоді, коли час евакуації не перевищує час настання критичної фази розвитку пожежі, тобто часу від початку пожежі до досягнення граничних для людини впливів факторі пожежі (критичних температур, ступені задимлення, зниження концентрації кисню и т.п.). Число евакуаційних виходів повинно бути не менш двох. Вони повинні розташовуватися розосереджено. Мінімальна відстань l між найбільш віддаленими один від одного евакуаційними виходами із приміщення визначається за формулою:

                                                ,                                            (4.23)

де Р – периметр приміщення, м.

Двері на шляхах евакуації повинні відкриватися у напрямку виходу із будівлі.

У кожному приміщенні на видному місці повинен бути вивішений план евакуації при пожежі.

При пожежі обов'язково необхідно враховувати небезпечні чинники і механізм їх дії на людину.

При виникненні пожежі, після виклику пожежної охорони, необхідно попередити про це усіх, хто знаходиться поруч, після чого евакуюватися самому і по можливості допомогти евакуюватися іншим, особливо особам літнього віку і дітям, попереджаючи при цьому виникнення паніки. З метою обмеження циркуляції повітря, яке здатне збільшувати швидкість горіння, покидаючи приміщення, закрийте за собою усі двері, якщо це можливо. Перекрійте газ і відключіть електрику, але слід пам'ятати, що в першу чергу завжди необхідно покидати активну зону горіння, тобто місце в районі пожежі, де є присутнім задимлення і підвищена температура.
      Якщо пожежа виникла в приміщенні над вами і безпосередньої загрози для вас не спостерігається, то бажано виконати заходи по зниженню можливих втрат від води яку проливають при гасінні пожежі. Для цього необхідно відключити всі електроприводи та прикрити їх поліетиленовою плівкою. Значно гірше, якщо пожежа виникла в приміщенні над вами – потрібно оцінити обстановку и якщо є впевненість, що ще не має сильної задимленості з високою температурою, потрібно негайно покинути приміщення, рухаючись до виходу по коридорам і сходовим кліткам.

Користуватися ліфтом категорично забороняється, за винятком ліфтів, які спеціально призначені для транспортування пожежної охорони. Шахта ліфта є шляхом для поширення диму і отруйних продуктів горіння, до того ж при пожежі ліфт часто відключають і можна опинитися в пастці при пожежі.

Якщо ви знаходитеся в приміщенні де немає пожежі, но відрізани вогнем, димом, високою температурою від головних шляхів евакуації, то в першу чергу необхідно заважити доступу диму и продуктів горіння в це приміщення. Для чого необхідно негайно закрити усі щілини у дверях та під ними змоченими водою ганчірками, рушниками, робочими халатами та інш.

Якщо приміщення все ж заповнено димом, необхідно підповзти до вікна, закрити при цьому рот та ніс змоченою тканиною, яка грає роль фільтру та в певної мірі захищає від продуктів горіння.

Рухатись у задимленій зоні поповзом або максимально пригнувшись, необхідно тому що більшість нагрітих газоподібних отруйних речовин та дим збираються у верхній зоні приміщення, окрім цього, в приміщенні при горінні температура на рівні очей людини у 6 разів вище за температуру на рівні полу, до того ж внизу завжди зберігається більша концентрація кисню. Коли ви опинились біля вікна трохи відкрийте його та дихайте через щілину, очікуючи прибуття пожежників. При їх прибутті негайно зверніть на себе увагу. Ніколи не стрибайте через вікно без відомої на це необхідності.

ВИСНОВКИ

 Результати магістерської роботи відповідають поставленим у вступі задачам. У роботі досліджений вбудований в мобільний пристрій акселерометр і розроблений універсальний алгоритм підрахунку фізичних вправ і збереження результатів в базу даних для ведення спортивної статистики. Ці дослідження об'єднані в кінцевий програмний продукт для смартфонів iPhone. Організована синхронізація статистичних даних з сервером. Розроблена відповідна соціальна мережа з рядом певних функцій, орієнтованих на ведення статистики. Проведена заміна сервера на новіший і надійніший і оптимізація початкового коду веб-сервісу з метою збільшення його швидкодії і надійності.

Кінцевий продукт (зв'язка мобільного додатку і соціальної мережі) був протестований на реальній аудиторії. При максимальному навантаженні на сервер від розроблених раніше додатків для соціальної мережі (200 запитів в секунду) останній був завантажений усього на 1%. Таким чином можна зробити висновок, що сервер з легкістю витримає більше 1000 запитів за секунду, якщо запити будуть не більше 10 кілобайт. На момент завершення магістерської роботи (початок грудня 2012 року) SportDiary набрав 300 користувачів. За весь час не поступало жодної критичної помилки від додатків і сервер не давав збоїв.

В якості доповнення на базі використаних технологій була розроблена демонстраційна версія веб-сервісу прийому замовлень для служб таксі, що повністю готова для презентації потенційним клієнтам.

Надалі планується розробка ще декількох додатків для мобільних пристроєв, а також розробка додатків для соціальних мереж на базі опанованих технологій.

ПЕРЕЛІК ПОСИЛАНЬ

 

1. Shawn Grimes, Colin Francis. IOS 5 Recipes. A Problem-Solution Approach. / Shawn Grimes, Colin Francis. — Apress, 2012 — p. 353.

2. Томсон Л. Разработка Web-приложений на PHP и MySQL / Л. Томсон, Л. Веллинг. – К.: "ДиаСофт", 2001. - 22 с.

3. Lerner Michael. Building worldwide Web sites: [Електронний ресурс]. — Режим доступу: http://www.ibm.com/developerworks/library/web-localization.html

4. Matt Neuburg. Programming iOS 5. / Matt Neuburg . — O’Reilly, 2012 — p. 873.

5. Далримпл Марк, Кнастер Скотт. Objective-C 2.0 и программирование для Mac. / Далримпл Марк, Кнастер Скотт. — Издательский дом «Вильямс», 2010  — с. 166.

6. Документація з MySQL: [Електронний ресурс]. — Режим доступу: http://www.mysql.ru/docs/tnastroyka.html

7. Wei-Meng Lee. Beginning iOS 5 Application Development. / Wei-Meng Lee . — John Wiley & Sons, 2012 — p. 11-13.

8. Patrick Alessi. Beginning iOS Game Development. / Patrick Alessi. — Wiley Publishing, 2012 — p. 13.

9. Настройка и оптимизация MySQL сервера: [Електронний ресурс]. — Режим доступу: http://habrahabr.ru/post/108418/

10. Apple Inc. iOS Human Interface Guidelines, 2012 — p. 14.

11. Довідкова література з nginx: [Електронний ресурс]. — Режим доступу: http://nginx.org/ru/

12. Ling Luo, Chen Zhong, Qiao Pan Mu, Yao Huang, Xiao Sun Ling. Improve the performance of your web applications:  [Електронний ресурс]. — Режим доступу: http://www.ibm.com/developerworks/web/library/wa-webappperformance/

 13. Закон України «Про охорону праці» в редакції від 21 листопада 2002 року.

14. Жидецький В.Ц. Охорона праці користувачів комп'ютерів. Навчальний посібник / В. Жидецький  – Вид. 2-ге, доп. – Львів: Афіша, 2000 – с. 176.

15. Навакатікян О.О. Охорона праці користувачів комп'ютерних відеодисплейних терміналів / О.О.  Навакатікян, В.В. Кальниш, С.М. Стрюков  – К., 1997. – с. 400.

Додаток А. вихідний код основного модулю проекту

//

//  TrainingViewController.m

//  SportDiary

//

//  Created by Iegor Malovanyi on 07.07.12.

//  Copyright (c) 2012 Iegor Malovanyi. All rights reserved.

//

#import "TrainingViewController.h"

#import <AVFoundation/AVFoundation.h>

#import "sqlite3.h"

#define kAccelerometerFrequency       20.0f //Hz

#define databaseFilename @"ExercisesDB.sqlite"

const int PORTRAIT_UP = 1;

const int PORTRAIT_DOWN = 2;

const int LANDSCAPE_RIGHT = 3;

const int LANDSCAPE_LEFT = 4;

const int FACE_UP = 5;

const int FACE_DOWN = 6;

const int UP = 1; //pull ups

const int DOWN = 2; //deeps

const int START = 0;

const int STOP = 1;

const int SAVE = 2;

@interface TrainingViewController () {

   double _maxX;

   double _maxY;

   double _maxZ;

   int _repsCounter;

   int _startCounter;

   int _stopCounter;

   BOOL _motionUp;

   BOOL _motionDown;

   BOOL _startReps;

   int _exerciseStarted;

   int _deviceOrientation;

   int _upOrDown;

   AVAudioPlayer *audioPlayer;

   

   sqlite3 *exercisesDB;

}

@end

@implementation TrainingViewController

@synthesize repsLabel;

@synthesize motionManager;

@synthesize updateTimer;

@synthesize exerciseButton;

@synthesize exitButton;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil

{

   self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];

   if (self) {

       // Custom initialization

   }

   return self;

}

- (void)viewDidLoad

{

   [super viewDidLoad];

// Do any additional setup after loading the view.

   

   UIImage *backgroundImage = [UIImage imageNamed:@"btn604_180.png"];

   [exerciseButton setBackgroundImage:backgroundImage forState:UIControlStateNormal];

   NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/startsound.mp3", [[NSBundle mainBundle] resourcePath]]];

   

   NSError *error;

   audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];

   audioPlayer.numberOfLoops = 0;

   _exerciseStarted = START;

}

-(void)accelUpdate

{

   if(self.motionManager.accelerometerAvailable) {

       CMAccelerometerData *accelerometerData = self.motionManager.accelerometerData;

       if (_startCounter < 100)

       {  

           switch (_startCounter) {

               case 0:

                   self.exerciseButton.enabled = NO;

                   self.exitButton.enabled = NO;

                   [exerciseButton setTitle:@"Get Ready (5)" forState:UIControlStateNormal];

                   break;

                   

               case 20:

                   [exerciseButton setTitle:@"Get Ready (4)" forState:UIControlStateNormal];

                   break;

                   

               case 40:

                   [exerciseButton setTitle:@"Get Ready (3)" forState:UIControlStateNormal];

                   

                   break;

                   

               case 60:

                   [exerciseButton setTitle:@"Get Ready (2)" forState:UIControlStateNormal];

                   break;

                   

               case 80:

                   [exerciseButton setTitle:@"Get Ready (1)" forState:UIControlStateNormal];

                   break;

                   

               default:

                   break;

           }

           if (_startCounter == 99)

           {

               [exerciseButton setTitle:@"Stop" forState:UIControlStateNormal];

               self.exerciseButton.enabled = YES;

               if (accelerometerData.acceleration.y < -0.7)

               {

                   _deviceOrientation = PORTRAIT_UP;

               }

               else if (accelerometerData.acceleration.y > 0.7)

               {

                   _deviceOrientation = PORTRAIT_DOWN;

               }

               else if (accelerometerData.acceleration.z < -0.7)

               {

                   _deviceOrientation = FACE_UP;

               }

               else if (accelerometerData.acceleration.z > 0.7)

               {

                   _deviceOrientation = FACE_DOWN;

               }

               else if (accelerometerData.acceleration.x < -0.7)

               {

                   _deviceOrientation = LANDSCAPE_RIGHT;

               }

               else if (accelerometerData.acceleration.x > 0.7)

               {

                   _deviceOrientation = LANDSCAPE_LEFT;

               }

               else

               {

                   _deviceOrientation = PORTRAIT_UP;

               }

               

               if (audioPlayer != nil)

                   [audioPlayer play];

           }

           _startCounter++;

       }

       else

       {

           _stopCounter++;

           if (_stopCounter >= 100)

           {

               if (audioPlayer != nil)

                   [audioPlayer play];

               _exerciseStarted = SAVE;

               self.exitButton.enabled = YES;

               [self setMotionManager:nil];

               [self setUpdateTimer:nil];

               [exerciseButton setTitle:@"Save" forState:UIControlStateNormal];

           }

           else

           {

               switch (_deviceOrientation) {

                   case PORTRAIT_UP:

                       

                       if (!_startReps)

                       {

                           if (accelerometerData.acceleration.y < -1.3)

                           {

                               _startReps = TRUE;

                               _motionDown = TRUE;

                               _stopCounter = 0;

                               _upOrDown = UP;

                           } else if (accelerometerData.acceleration.y > -0.7)

                           {

                               _startReps = TRUE;

                               _motionUp = TRUE;

                               _stopCounter = 0;

                               _upOrDown = DOWN;

                           }

                       }

                       else

                       {

                           if (_upOrDown == UP)

                           {

                               if ((accelerometerData.acceleration.y > -0.7) && (_motionDown) && (!_motionUp))

                               {

                                   _motionUp = TRUE;

                                   _motionDown = FALSE;

                                   _repsCounter++;

                                   self.repsLabel.text = [NSString stringWithFormat:@"%i", _repsCounter];

                                   _stopCounter = 0;

                               }

                               else if ((accelerometerData.acceleration.y < -1.3) && (_motionUp) && (!_motionDown))

                               {

                                   _motionDown = TRUE;

                                   _motionUp = FALSE;

                                   _stopCounter = 0;

                               }

                           }

                           if (_upOrDown == DOWN)

                           {

                               if ((accelerometerData.acceleration.y < -1.3) && (_motionUp) && (!_motionDown))

                               {

                                   _motionDown = TRUE;

                                   _motionUp = FALSE;

                                   _repsCounter++;

                                   self.repsLabel.text = [NSString stringWithFormat:@"%i", _repsCounter];

                                   _stopCounter = 0;

                               }

                               else if ((accelerometerData.acceleration.y > -0.7) && (_motionDown) && (!_motionUp))

                               {

                                   _motionUp = TRUE;

                                   _motionDown = FALSE;

                                   _stopCounter = 0;

                               }

                           }

                       }

                       

                       break;

                       

                   case PORTRAIT_DOWN:

                       

                       if (!_startReps)

                       {

                           if (accelerometerData.acceleration.y < 0.7)

                           {

                               _startReps = TRUE;

                               _motionDown = TRUE;

                               _stopCounter = 0;

                               _upOrDown = UP;

                           } else if (accelerometerData.acceleration.y > 1.3)

                           {

                               _startReps = TRUE;

                               _motionUp = TRUE;

                               _stopCounter = 0;

                               _upOrDown = DOWN;

                           }

                       }

                       else

                       {

                           if (_upOrDown == UP)

                           {

                               if ((accelerometerData.acceleration.y > 1.3) && (_motionDown) && (!_motionUp))

                               {

                                   _motionUp = TRUE;

                                   _motionDown = FALSE;

                                   _repsCounter++;

                                   self.repsLabel.text = [NSString stringWithFormat:@"%i", _repsCounter];

                                   _stopCounter = 0;

                               }

                               else if ((accelerometerData.acceleration.y < 0.7) && (_motionUp) && (!_motionDown))

                               {

                                   _motionDown = TRUE;

                                   _motionUp = FALSE;

                                   _stopCounter = 0;

                               }

                           }

                           if (_upOrDown == DOWN)

                           {

                               if ((accelerometerData.acceleration.y < 0.7) && (_motionUp) && (!_motionDown))

                               {

                                   _motionDown = TRUE;

                                   _motionUp = FALSE;

                                   _repsCounter++;

                                   self.repsLabel.text = [NSString stringWithFormat:@"%i", _repsCounter];

                                   _stopCounter = 0;

                               }

                               else if ((accelerometerData.acceleration.y > 1.3) && (_motionDown) && (!_motionUp))

                               {

                                   _motionUp = TRUE;

                                   _motionDown = FALSE;

                                   _stopCounter = 0;

                               }

                           }

                       }

                       

                       break;

                       

                   case LANDSCAPE_RIGHT:

                       

                       if (!_startReps)

                       {

                           if (accelerometerData.acceleration.x < -1.3)

                           {

                               _startReps = TRUE;

                               _motionDown = TRUE;

                               _stopCounter = 0;

                               _upOrDown = UP;

                           } else if (accelerometerData.acceleration.x > -0.7)

                           {

                               _startReps = TRUE;

                               _motionUp = TRUE;

                               _stopCounter = 0;

                               _upOrDown = DOWN;

                           }

                       }

                       else

                       {

                           if (_upOrDown == UP)

                           {

                               if ((accelerometerData.acceleration.x > -0.7) && (_motionDown) && (!_motionUp))

                               {

                                   _motionUp = TRUE;

                                   _motionDown = FALSE;

                                   _repsCounter++;

                                   self.repsLabel.text = [NSString stringWithFormat:@"%i", _repsCounter];

                                   _stopCounter = 0;

                               }

                               else if ((accelerometerData.acceleration.x < -1.3) && (_motionUp) && (!_motionDown))

                               {

                                   _motionDown = TRUE;

                                   _motionUp = FALSE;

                                   _stopCounter = 0;

                               }

                           }

                           if (_upOrDown == DOWN)

                           {

                               if ((accelerometerData.acceleration.x < -1.3) && (_motionUp) && (!_motionDown))

                               {

                                   _motionDown = TRUE;

                                   _motionUp = FALSE;

                                   _repsCounter++;

                                   self.repsLabel.text = [NSString stringWithFormat:@"%i", _repsCounter];

                                   _stopCounter = 0;

                               }

                               else if ((accelerometerData.acceleration.x > -0.7) && (_motionDown) && (!_motionUp))

                               {

                                   _motionUp = TRUE;

                                   _motionDown = FALSE;

                                   _stopCounter = 0;

                               }

                           }

                       }

                       

                       break;

                       

                   case LANDSCAPE_LEFT:

                       

                       if (!_startReps)

                       {

                           if (accelerometerData.acceleration.x < 0.7)

                           {

                               _startReps = TRUE;

                               _motionDown = TRUE;

                               _stopCounter = 0;

                               _upOrDown = UP;

                           } else if (accelerometerData.acceleration.x > 1.3)

                           {

                               _startReps = TRUE;

                               _motionUp = TRUE;

                               _stopCounter = 0;

                               _upOrDown = DOWN;

                           }

                       }

                       else

                       {

                           if (_upOrDown == UP)

                           {

                               if ((accelerometerData.acceleration.x > 1.3) && (_motionDown) && (!_motionUp))

                               {

                                   _motionUp = TRUE;

                                   _motionDown = FALSE;

                                   _repsCounter++;

                                   self.repsLabel.text = [NSString stringWithFormat:@"%i", _repsCounter];

                                   _stopCounter = 0;

                               }

                               else if ((accelerometerData.acceleration.x < 0.7) && (_motionUp) && (!_motionDown))

                               {

                                   _motionDown = TRUE;

                                   _motionUp = FALSE;

                                   _stopCounter = 0;

                               }

                           }

                           if (_upOrDown == DOWN)

                           {

                               if ((accelerometerData.acceleration.x < 0.7) && (_motionUp) && (!_motionDown))

                               {

                                   _motionDown = TRUE;

                                   _motionUp = FALSE;

                                   _repsCounter++;

                                   self.repsLabel.text = [NSString stringWithFormat:@"%i", _repsCounter];

                                   _stopCounter = 0;

                               }

                               else if ((accelerometerData.acceleration.x > 1.3) && (_motionDown) && (!_motionUp))

                               {

                                   _motionUp = TRUE;

                                   _motionDown = FALSE;

                                   _stopCounter = 0;

                               }

                           }

                       }

                       

                       break;

                       

                   case FACE_UP:

                       

                       if (!_startReps)

                       {

                           if (accelerometerData.acceleration.z < -1.3)

                           {

                               _startReps = TRUE;

                               _motionDown = TRUE;

                               _stopCounter = 0;

                               _upOrDown = UP;

                           } else if (accelerometerData.acceleration.z > -0.7)

                           {

                               _startReps = TRUE;

                               _motionUp = TRUE;

                               _stopCounter = 0;

                               _upOrDown = DOWN;

                           }

                       }

                       else

                       {

                           if (_upOrDown == UP)

                           {

                               if ((accelerometerData.acceleration.z > -0.7) && (_motionDown) && (!_motionUp))

                               {

                                   _motionUp = TRUE;

                                   _motionDown = FALSE;

                                   _repsCounter++;

                                   self.repsLabel.text = [NSString stringWithFormat:@"%i", _repsCounter];

                                   _stopCounter = 0;

                               }

                               else if ((accelerometerData.acceleration.z < -1.3) && (_motionUp) && (!_motionDown))

                               {

                                   _motionDown = TRUE;

                                   _motionUp = FALSE;

                                   _stopCounter = 0;

                               }

                           }

                           if (_upOrDown == DOWN)

                           {

                               if ((accelerometerData.acceleration.z < -1.3) && (_motionUp) && (!_motionDown))

                               {

                                   _motionDown = TRUE;

                                   _motionUp = FALSE;

                                   _repsCounter++;

                                   self.repsLabel.text = [NSString stringWithFormat:@"%i", _repsCounter];

                                   _stopCounter = 0;

                               }

                               else if ((accelerometerData.acceleration.z > -0.7) && (_motionDown) && (!_motionUp))

                               {

                                   _motionUp = TRUE;

                                   _motionDown = FALSE;

                                   _stopCounter = 0;

                               }

                           }

                       }

                       

                       break;

                       

                   case FACE_DOWN:

                       

                       if (!_startReps)

                       {

                           if (accelerometerData.acceleration.z < 0.7)

                           {

                               _startReps = TRUE;

                               _motionDown = TRUE;

                               _stopCounter = 0;

                               _upOrDown = UP;

                           } else if (accelerometerData.acceleration.z > 1.3)

                           {

                               _startReps = TRUE;

                               _motionUp = TRUE;

                               _stopCounter = 0;

                               _upOrDown = DOWN;

                           }

                       }

                       else

                       {

                           if (_upOrDown == UP)

                           {

                               if ((accelerometerData.acceleration.z > 1.3) && (_motionDown) && (!_motionUp))

                               {

                                   _motionUp = TRUE;

                                   _motionDown = FALSE;

                                   _repsCounter++;

                                   self.repsLabel.text = [NSString stringWithFormat:@"%i", _repsCounter];

                                   _stopCounter = 0;

                               }

                               else if ((accelerometerData.acceleration.z < 0.7) && (_motionUp) && (!_motionDown))

                               {

                                   _motionDown = TRUE;

                                   _motionUp = FALSE;

                                   _stopCounter = 0;

                               }

                           }

                           if (_upOrDown == DOWN)

                           {

                               if ((accelerometerData.acceleration.z < 0.7) && (_motionUp) && (!_motionDown))

                               {

                                   _motionDown = TRUE;

                                   _motionUp = FALSE;

                                   _repsCounter++;

                                   self.repsLabel.text = [NSString stringWithFormat:@"%i", _repsCounter];

                                   _stopCounter = 0;

                               }

                               else if ((accelerometerData.acceleration.z > 1.3) && (_motionDown) && (!_motionUp))

                               {

                                   _motionUp = TRUE;

                                   _motionDown = FALSE;

                                   _stopCounter = 0;

                               }

                           }

                       }

                       

                       break;

                       

                       

                   default:

                       break;

               }

                       

           }

       }

   }

}

- (void)viewDidUnload

{

   [super viewDidUnload];

   // Release any retained subviews of the main view.

   

   [self setMotionManager:nil];

   [self setRepsLabel:nil];

   [self setUpdateTimer:nil];

   [self setExerciseButton:nil];

   

   [self setExitButton:nil];

}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation

{

   return (interfaceOrientation == UIInterfaceOrientationPortrait);

}

- (IBAction)startButton:(id)sender {

   if (_exerciseStarted == START)

   {

       _exerciseStarted = STOP;

       

       self.motionManager = [[CMMotionManager alloc]init];

       

       if (self.motionManager.accelerometerAvailable) {

           self.motionManager.accelerometerUpdateInterval = 1.0f / kAccelerometerFrequency ;

           [self.motionManager startAccelerometerUpdates];

       }

       

       self.updateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f / kAccelerometerFrequency

                                                           target:self

                                                         selector:@selector(accelUpdate)

                                                         userInfo:nil

                                                          repeats:YES];

       _repsCounter = 0;

       _motionUp = FALSE;

       _motionDown = FALSE;

       _startReps = FALSE;

       _startCounter = 0;

       _stopCounter = 0;

       _upOrDown = 0;

       self.repsLabel.text = @"0";

   }

   else if (_exerciseStarted == STOP)

   {

       _exerciseStarted = SAVE;

       self.exitButton.enabled = YES;

       

       [self setMotionManager:nil];

       [self setUpdateTimer:nil];

       [exerciseButton setTitle:@"Save" forState:UIControlStateNormal];

   }

   else if (_exerciseStarted == SAVE)

   {

       if (_repsCounter != 0)

       {

           NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

           NSString *dbCopyPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:databaseFilename];

           NSUserDefaults * userDefaults = [NSUserDefaults standardUserDefaults];

           NSString * exerciseID = [userDefaults objectForKey:@"ExerciseID"];

           

           NSDate * today = [NSDate dateWithTimeIntervalSinceNow: 0];

           NSDateFormatter * date_format = [[NSDateFormatter alloc] init];

           [date_format setDateFormat:@"yyyy/MM/dd"];

           NSString * todayDate = [date_format stringFromDate: today];

           BOOL isRecordInTable = FALSE;

           

           if (sqlite3_open([dbCopyPath UTF8String], &exercisesDB) == SQLITE_OK)

           {

               //Database opened successfull

              

               NSString *queryStatement = [NSString stringWithFormat:@"SELECT UniqueID, ExerciseDate, Reps FROM Statistics WHERE UniqueID='%@' AND ExerciseDate='%@'", exerciseID, todayDate];

               

               sqlite3_stmt *statement;

               if (sqlite3_prepare_v2(exercisesDB, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)

               {

                   if (sqlite3_step(statement) != SQLITE_DONE) {

                       isRecordInTable = TRUE;

                   }

                   sqlite3_finalize(statement);

               }

               

               if (isRecordInTable)

               {

                   queryStatement = [NSString stringWithFormat:@"UPDATE Statistics SET Reps=Reps+%i WHERE UniqueID='%@' AND ExerciseDate='%@'", _repsCounter, exerciseID, todayDate];

                   if (sqlite3_prepare_v2(exercisesDB, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)

                   {

                       sqlite3_step(statement);

                       sqlite3_finalize(statement);

                   }

               }

               else

               {

                   queryStatement = [NSString stringWithFormat:@"INSERT INTO Statistics (UniqueID, ExerciseDate, Reps) VALUES ('%@', '%@', %i)", exerciseID, todayDate, _repsCounter];

                   if (sqlite3_prepare_v2(exercisesDB, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)

                   {

                       sqlite3_step(statement);

                       sqlite3_finalize(statement);

                   }

               }

               

               sqlite3_close(exercisesDB);

               

           } else {

               //Failed to open database

           }

           

           NSString * needSync = @"YES";

           [[NSUserDefaults standardUserDefaults] setObject:needSync forKey:@"NeedSync"];

           

           NSString * isInternet = [[NSUserDefaults standardUserDefaults] objectForKey:@"IsInternet"];

           if ([isInternet isEqualToString:@"YES"])

           {

               UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Synchronizing"

                                                                   message:@"\n"

                                                                  delegate:self

                                                         cancelButtonTitle:nil

                                                         otherButtonTitles:nil];

               

               UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];   

               spinner.center = CGPointMake(142.5, 75.5); // .5 so it doesn't blur

               [alertView addSubview:spinner];

               [spinner startAnimating];

               [alertView show];

               

               //Form string for request////////////////////

               NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

               NSString *dbCopyPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:databaseFilename];

               NSString * exercName;

               int numberOfRecords = 0;

               NSString * exercID;

               NSString * exerciseDate;

               int numberOfReps;

               

               NSString * userLog;

               NSString * userPass;

               NSString * syncDate;

               

               NSString * params;

               

               if (sqlite3_open([dbCopyPath UTF8String], &exercisesDB) == SQLITE_OK)

               {

                   

                   //get  pass, name

                   

                   NSString *queryStatement = @"SELECT UserName, UserLogin, UserPassword, IsNameChanged, IsPhotoChanged, SyncDate, UserID FROM PersonalData";

                   

                   sqlite3_stmt *statement;

                   if (sqlite3_prepare_v2(exercisesDB, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)

                   {

                       if (sqlite3_step(statement) != SQLITE_DONE) {

                           userLog = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)];

                           userPass = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 2)];

                           syncDate = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 5)];

                       }

                       sqlite3_finalize(statement);

                   }

                   

                   //Get number of exercises

                   queryStatement = @"SELECT COUNT(*) as cnt1 FROM Exercises";

                   

                   if (sqlite3_prepare_v2(exercisesDB, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)

                   {

                       if (sqlite3_step(statement) != SQLITE_DONE) {

                           numberOfRecords = sqlite3_column_int(statement, 0);

                           int number = rand();

                           

                           params = [NSString stringWithFormat:@"rand=%i&sync_date=%@&login=%@&pass=%@&caption_count=%i", number, syncDate, userLog, userPass, numberOfRecords];

                       }

                       sqlite3_finalize(statement);

                   }

                   

                   //Get names of exercises

                   queryStatement = [NSString stringWithFormat:@"SELECT Name, UniqueID FROM Exercises"];

                   NSString * oneExercise = @"";

                   int counter = 0;

                   

                   if (sqlite3_prepare_v2(exercisesDB, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)

                   {

                       while (sqlite3_step(statement) == SQLITE_ROW) {

                           exercName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 0)];

                           oneExercise = [NSString stringWithFormat:@"&caption%i=%@", counter, exercName];

                           params = [params stringByAppendingString:oneExercise];

                           counter++;

                       }

                       sqlite3_finalize(statement);

                   }

                   

                   

                   //get statistics

                   

                   queryStatement = @"SELECT UniqueID, ExerciseDate, Reps FROM Statistics";

                   counter = 0;

                   NSString * exercisesString = @"";

                   NSDateFormatter * date_format = [[NSDateFormatter alloc] init];

                   

                   [date_format setDateFormat:@"yyyy/MM/dd"];

                   NSDate *dateFromString = [[NSDate alloc] init];

                   NSDate *dateSyncronization = [[NSDate alloc] init];

                   dateSyncronization = [date_format dateFromString:syncDate];

                   

                   if (sqlite3_prepare_v2(exercisesDB, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)

                   {

                       while (sqlite3_step(statement) == SQLITE_ROW) {

                           exercID = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 0)];

                           exerciseDate = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)];

                           numberOfReps = sqlite3_column_int(statement, 2);

                           dateFromString = [date_format dateFromString:exerciseDate];

                           

                           if (([dateFromString compare:dateSyncronization] == NSOrderedSame) || ([dateFromString compare:dateSyncronization] == NSOrderedDescending))

                           {

                               oneExercise = [NSString stringWithFormat:@"&cid%i=%@&cc%i=%i&cd%i=%@", counter, exercID, counter, numberOfReps, counter, exerciseDate];

                               exercisesString = [exercisesString stringByAppendingString:oneExercise];

                               counter++;

                           }

                       }

                       

                       oneExercise = [NSString stringWithFormat:@"&count_counts=%i", counter];

                       params = [params stringByAppendingString:oneExercise];

                       params = [params stringByAppendingString:exercisesString];

                       sqlite3_finalize(statement);

                   }

                   

                   

                   NSData *postData = [params dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO];

                   

                   NSString *postLength = [NSString stringWithFormat:@"%d", [postData length]];

                   

                   int num = rand();

                   

                   NSString * reqURl = [NSString stringWithFormat:@"http://sportdiary.net/api/sync_d.php?rand=%i", num];

                   NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:reqURl]

                                                                          cachePolicy:NSURLRequestReloadIgnoringLocalCacheData

                                                                      timeoutInterval:5.0];

                   

                   [request setHTTPMethod:@"POST"];

                   [request setValue:postLength forHTTPHeaderField:@"Content-Length"];

                   [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];

                   [request setHTTPBody:postData];

                   

                   

                   NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];

                   

                   if(responseData){

                       NSError * error;

                       NSDictionary * respond = [NSJSONSerialization JSONObjectWithData:responseData

                                                                                options:kNilOptions

                                                                                  error:&error];

                       NSString * status = [respond objectForKey:@"status"];

                       

                       if ([status isEqualToString:@"ok"])

                       {

                           NSDate * today = [NSDate dateWithTimeIntervalSinceNow: 0];

                           NSDateFormatter * date_format = [[NSDateFormatter alloc] init];

                           

                           [date_format setDateFormat:@"yyyy/MM/dd"];

                           NSString * todayDate = [date_format stringFromDate: today];

                           

                           queryStatement = [NSString stringWithFormat:@"UPDATE PersonalData SET SyncDate='%@'", todayDate];

                           if (sqlite3_prepare_v2(exercisesDB, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)

                           {

                               sqlite3_step(statement);

                               sqlite3_finalize(statement);

                           }

                           NSString * needSync = @"NO";

                           [[NSUserDefaults standardUserDefaults] setObject:needSync forKey:@"NeedSync"];

                       }

                   }

                   

                   

                   sqlite3_close(exercisesDB);

               } else {

                   //Failed to open database

               }

               

               [alertView dismissWithClickedButtonIndex:0 animated:YES];

               ////////////////////////////////////////////////////////

           }

       }

       [self.presentingViewController dismissModalViewControllerAnimated:YES];

   }

}

- (IBAction)doneButton:(id)sender {

   if (_repsCounter != 0)

   {

       NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

       NSString *dbCopyPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:databaseFilename];

       NSUserDefaults * userDefaults = [NSUserDefaults standardUserDefaults];

       NSString * exerciseID = [userDefaults objectForKey:@"ExerciseID"];

       

       NSDate * today = [NSDate dateWithTimeIntervalSinceNow: 0];

       NSDateFormatter * date_format = [[NSDateFormatter alloc] init];

       

       [date_format setDateFormat:@"yyyy/MM/dd"];

       NSString * todayDate = [date_format stringFromDate: today];

       BOOL isRecordInTable = FALSE;

       

       if (sqlite3_open([dbCopyPath UTF8String], &exercisesDB) == SQLITE_OK)

       {

           //Database opened successfull

           

           NSString *queryStatement = [NSString stringWithFormat:@"SELECT UniqueID, ExerciseDate, Reps FROM Statistics WHERE UniqueID='%@' AND ExerciseDate='%@'", exerciseID, todayDate];

           

           sqlite3_stmt *statement;

           if (sqlite3_prepare_v2(exercisesDB, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)

           {

               if (sqlite3_step(statement) != SQLITE_DONE) {

                   isRecordInTable = TRUE;

               }

               sqlite3_finalize(statement);

           }

           

           if (isRecordInTable)

           {

               queryStatement = [NSString stringWithFormat:@"UPDATE Statistics SET Reps=Reps+%i WHERE UniqueID='%@' AND ExerciseDate='%@'", _repsCounter, exerciseID, todayDate];

               if (sqlite3_prepare_v2(exercisesDB, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)

               {

                   sqlite3_step(statement);

                   sqlite3_finalize(statement);

               }

               

           }

           else

           {

               queryStatement = [NSString stringWithFormat:@"INSERT INTO Statistics (UniqueID, ExerciseDate, Reps) VALUES ('%@', '%@', %i)", exerciseID, todayDate, _repsCounter];

               if (sqlite3_prepare_v2(exercisesDB, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)

               {

                   sqlite3_step(statement);

                   sqlite3_finalize(statement);

               }

           }

           

           sqlite3_close(exercisesDB);

           

           

       } else {

           //Failed to open database

       }

       

       NSString * needSync = @"YES";

       [[NSUserDefaults standardUserDefaults] setObject:needSync forKey:@"NeedSync"];

       

       NSString * isInternet = [[NSUserDefaults standardUserDefaults] objectForKey:@"IsInternet"];

       if ([isInternet isEqualToString:@"YES"])

       {

           

           UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Synchronizing"

                                                               message:@"\n"

                                                              delegate:self

                                                     cancelButtonTitle:nil

                                                     otherButtonTitles:nil];

           

           UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];   

           spinner.center = CGPointMake(142.5, 75.5); // .5 so it doesn't blur

           [alertView addSubview:spinner];

           [spinner startAnimating];

           [alertView show];

           

           //Form string for request////////////////////

           NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

           NSString *dbCopyPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:databaseFilename];

           NSString * exercName;

           int numberOfRecords = 0;

           NSString * exercID;

           NSString * exerciseDate;

           int numberOfReps;

           

           NSString * userLog;

           NSString * userPass;

           NSString * syncDate;

           

           NSString * params;

           

           if (sqlite3_open([dbCopyPath UTF8String], &exercisesDB) == SQLITE_OK)

           {

               

               //get  pass, name

               

               NSString *queryStatement = @"SELECT UserName, UserLogin, UserPassword, IsNameChanged, IsPhotoChanged, SyncDate, UserID FROM PersonalData";

               

               sqlite3_stmt *statement;

               if (sqlite3_prepare_v2(exercisesDB, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)

               {

                   if (sqlite3_step(statement) != SQLITE_DONE) {

                       userLog = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)];

                       userPass = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 2)];

                       syncDate = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 5)];

                   }

                   sqlite3_finalize(statement);

               }

               

               //Get number of exercises

               queryStatement = @"SELECT COUNT(*) as cnt1 FROM Exercises";

               

               if (sqlite3_prepare_v2(exercisesDB, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)

               {

                   if (sqlite3_step(statement) != SQLITE_DONE) {

                       numberOfRecords = sqlite3_column_int(statement, 0);

                       int number = rand();

                       

                       params = [NSString stringWithFormat:@"rand=%i&sync_date=%@&login=%@&pass=%@&caption_count=%i", number, syncDate, userLog, userPass, numberOfRecords];

                   }

                   sqlite3_finalize(statement);

               }

               

               //Get names of exercises

               queryStatement = [NSString stringWithFormat:@"SELECT Name, UniqueID FROM Exercises"];

               NSString * oneExercise = @"";

               int counter = 0;

               

               if (sqlite3_prepare_v2(exercisesDB, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)

               {

                   while (sqlite3_step(statement) == SQLITE_ROW) {

                       exercName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 0)];

                       oneExercise = [NSString stringWithFormat:@"&caption%i=%@", counter, exercName];

                       params = [params stringByAppendingString:oneExercise];

                       counter++;

                   }

                   sqlite3_finalize(statement);

               }

               

               

               //get statistics

               

               queryStatement = @"SELECT UniqueID, ExerciseDate, Reps FROM Statistics";

               counter = 0;

               NSString * exercisesString = @"";

               NSDateFormatter * date_format = [[NSDateFormatter alloc] init];

               

               [date_format setDateFormat:@"yyyy/MM/dd"];

               NSDate *dateFromString = [[NSDate alloc] init];

               NSDate *dateSyncronization = [[NSDate alloc] init];

               dateSyncronization = [date_format dateFromString:syncDate];

               

               if (sqlite3_prepare_v2(exercisesDB, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)

               {

                   while (sqlite3_step(statement) == SQLITE_ROW) {

                       exercID = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 0)];

                       exerciseDate = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)];

                       numberOfReps = sqlite3_column_int(statement, 2);

                       dateFromString = [date_format dateFromString:exerciseDate];

                       

                       if (([dateFromString compare:dateSyncronization] == NSOrderedSame) || ([dateFromString compare:dateSyncronization] == NSOrderedDescending))

                       {

                           oneExercise = [NSString stringWithFormat:@"&cid%i=%@&cc%i=%i&cd%i=%@", counter, exercID, counter, numberOfReps, counter, exerciseDate];

                           exercisesString = [exercisesString stringByAppendingString:oneExercise];

                           counter++;

                       }

                   }

                   

                   oneExercise = [NSString stringWithFormat:@"&count_counts=%i", counter];

                   params = [params stringByAppendingString:oneExercise];

                   params = [params stringByAppendingString:exercisesString];

                   sqlite3_finalize(statement);

               }

               

               

               NSData *postData = [params dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO];

               

               NSString *postLength = [NSString stringWithFormat:@"%d", [postData length]];

               

               int num = rand();

               

               NSString * reqURl = [NSString stringWithFormat:@"http://sportdiary.net/api/sync_d.php?rand=%i", num];

               NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:reqURl]

                                                                      cachePolicy:NSURLRequestReloadIgnoringLocalCacheData

                                                                  timeoutInterval:5.0];

               

               [request setHTTPMethod:@"POST"];

               [request setValue:postLength forHTTPHeaderField:@"Content-Length"];

               [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];

               [request setHTTPBody:postData];

               

               

               NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];

               

               if(responseData){

                   NSError * error;

                   NSDictionary * respond = [NSJSONSerialization JSONObjectWithData:responseData

                                                                            options:kNilOptions

                                                                              error:&error];

                   NSString * status = [respond objectForKey:@"status"];

                   

                   if ([status isEqualToString:@"ok"])

                   {

                       NSDate * today = [NSDate dateWithTimeIntervalSinceNow: 0];

                       NSDateFormatter * date_format = [[NSDateFormatter alloc] init];

                       

                       [date_format setDateFormat:@"yyyy/MM/dd"];

                       NSString * todayDate = [date_format stringFromDate: today];

                       

                       queryStatement = [NSString stringWithFormat:@"UPDATE PersonalData SET SyncDate='%@'", todayDate];

                       if (sqlite3_prepare_v2(exercisesDB, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)

                       {

                           sqlite3_step(statement);

                           sqlite3_finalize(statement);

                       }

                       NSString * needSync = @"NO";

                       [[NSUserDefaults standardUserDefaults] setObject:needSync forKey:@"NeedSync"];

                   }

               }

               

               

               sqlite3_close(exercisesDB);

           } else {

               //Failed to open database

           }

           

           [alertView dismissWithClickedButtonIndex:0 animated:YES];

           

           ////////////////////////////////////////////////////////

       }

   }

   [self.presentingViewController dismissModalViewControllerAnimated:YES];

}

@end

ДОДАТОК Б. КОНФІГУРАЦІЙНИЙ ФАЙЛ БАНКУ ДАНИХ MYSQL

#

# The MySQL database server configuration file.

#

# You can copy this to one of:

# - "/etc/mysql/my.cnf" to set global options,

# - "~/.my.cnf" to set user-specific options.

#

# One can use all long options that the program supports.

# Run program with --help to get a list of available options and with

# --print-defaults to see which it would actually understand and use.

#

# For explanations see

# http://dev.mysql.com/doc/mysql/en/server-system-variables.html

# This will be passed to all mysql clients

# It has been reported that passwords should be enclosed with ticks/quotes

# escpecially if they contain "#" chars...

# Remember to edit /etc/mysql/debian.cnf when changing the socket location.

[client]

port  = 3306

socket  = /var/run/mysqld/mysqld.sock

# Here is entries for some specific programs

# The following values assume you have at least 32M ram

# This was formally known as [safe_mysqld]. Both versions are currently parsed.

[mysqld_safe]

socket  = /var/run/mysqld/mysqld.sock

nice  = 0

[mysqld]

#

# * Basic Settings

#

user  = mysql

pid-file = /var/run/mysqld/mysqld.pid

socket  = /var/run/mysqld/mysqld.sock

port  = 3306

basedir  = /usr

datadir  = /var/lib/mysql

tmpdir  = /tmp

# lc-message-dir is unknown to MySQL 5.1

#lc-messages-dir = /usr/share/mysql

skip-external-locking

#

# Instead of skip-networking the default is now to listen only on

# localhost which is more compatible and is not less secure.

#bind-address  = 127.0.0.1

bind-address        = 0.0.0.0

#

# * Fine Tuning

#

key_buffer  = 16M

max_allowed_packet = 16M

thread_stack  = 192K

thread_cache_size       = 4096

# This replaces the startup script and checks MyISAM tables if needed

# the first time they are touched

myisam-recover         = BACKUP

max_connections        = 1600

back_log = 5000

max_connect_errors = 50

table_cache            = 2048

#thread_concurrency     = 10

#

# * Query Cache Configuration

#

query_cache_limit = 1M

query_cache_size        = 16M

sync_binlog = 0

max_heap_table_size = 64M

tmp_table_size = 64M

sort_buffer_size = 8M

binlog_cache_size = 1M

join_buffer_size = 2M

key_buffer_size = 8G

read_buffer_size = 1M

read_rnd_buffer_size = 24M

innodb_read_io_threads=4

innodb_write_io_threads=4

innodb_buffer_pool_size = 18G

innodb_log_buffer_size = 32M

#innodb_log_file_size = 1300M

#########

innodb_buffer_pool_instances = 4

innodb_log_files_in_group = 2

innodb_file_per_table = 1

#########

innodb_flush_log_at_trx_commit = 2

innodb_thread_concurrency = 14

innodb_sync_spin_loops=10

myisam_sort_buffer_size = 128M

myisam_max_sort_file_size = 10G

#myisam_max_extra_sort_file_size = 10G

#

# * Logging and Replication

#

# Both location gets rotated by the cronjob.

# Be aware that this log type is a performance killer.

# As of 5.1 you can enable the log at runtime!

#general_log_file        = /var/log/mysql/mysql.log

#general_log             = 1

#

# Error logging goes to syslog due to /etc/mysql/conf.d/mysqld_safe_syslog.cnf.

#

# Here you can see queries with especially long duration

#slow_query_log = 1

#slow_query_log_file = /var/log/mysql/mysql-slow.log

#long_query_time = 2

#log-queries-not-using-indexes

#

# The following can be used as easy to replay backup logs or for replication.

# note: if you are setting up a replication slave, see README.Debian about

#       other settings you may need to change.

#server-id  = 1

#log_bin   = /var/log/mysql/mysql-bin.log

expire_logs_days = 10

max_binlog_size         = 100M

#binlog_do_db  = include_database_name

#binlog_ignore_db = include_database_name

#

# * InnoDB

#

# InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/.

# Read the manual for more InnoDB related options. There are many!

#

# * Security Features

#

# Read the manual, too, if you want chroot!

# chroot = /var/lib/mysql/

#

# For generating SSL certificates I recommend the OpenSSL GUI "tinyca".

#

# ssl-ca=/etc/mysql/cacert.pem

# ssl-cert=/etc/mysql/server-cert.pem

# ssl-key=/etc/mysql/server-key.pem

[mysqldump]

quick

quote-names

max_allowed_packet = 16M

[mysql]

#no-auto-rehash # faster start of mysql but no tab completition

[isamchk]

key_buffer  = 16M

#

# * IMPORTANT: Additional settings that can override those from this file!

#   The files must end with '.cnf', otherwise they'll be ignored.

#

!includedir /etc/mysql/conf.d/

додаток В. вихідний код модулю відрисовки графіку СТАТИСТИКИ

//

//  UserStatisticsGraphView.m

//  SportDiary

//

//  Created by Iegor Malovanyi on 30.08.12.

//  Copyright (c) 2012 Iegor Malovanyi. All rights reserved.

//

#import "UserStatisticsGraphView.h"

@interface UserStatisticsGraphView ()

{

   int numberOfRecords;

}

@end

@implementation UserStatisticsGraphView

- (id)initWithFrame:(CGRect)frame

{

   self = [super initWithFrame:frame];

   if (self) {

       // Initialization code

   }

   return self;

}

- (void)drawLineGraphWithContext:(CGContextRef)ctx

{

   int * reps = (int*) malloc(numberOfRecords * sizeof(int));

   

   CGContextSetTextMatrix(ctx, CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, 0.0));

   CGContextSelectFont(ctx, "Helvetica", 9, kCGEncodingMacRoman);

   CGContextSetTextDrawingMode(ctx, kCGTextFill);

   CGContextSetFillColorWithColor(ctx, [[UIColor colorWithRed:0 green:0 blue:0 alpha:1.0] CGColor]);

   

   

   int j = 0;

   int maxReps = 1;

   

   NSString * exerciseID = [[NSUserDefaults standardUserDefaults] objectForKey:@"GotExerciseID"];

   NSString * userID = [[NSUserDefaults standardUserDefaults] objectForKey:@"GotUserID"];

   

   NSDictionary *tweets;

   int num = rand();

   

   NSString * request = [NSString stringWithFormat:@"http://sportdiary.net/api/getchartdata_ios.php?rand=%i&id=%@&caption_id=%@", num, userID, exerciseID];

   

   NSData* dataGet = [NSData dataWithContentsOfURL:

                   [NSURL URLWithString: request]];

   

   NSError* error;

   if ( dataGet )  // Will only get here if there's data

   {

       tweets = [NSJSONSerialization JSONObjectWithData:dataGet

                                                options:kNilOptions

                                                  error:&error];

   }

   NSArray *exerciseSets = [tweets valueForKeyPath:@"gyms"];

   NSString * buffer;

   

   if (numberOfRecords > 0)

   {

       for (int i = 0; i < numberOfRecords; i++)

       {

           buffer = [[exerciseSets objectAtIndex:i] objectForKey:@"cnt"];

           reps[j] = [buffer intValue];

           if (reps[j] > maxReps)

               maxReps = reps[j];

           NSString *theText = [[exerciseSets objectAtIndex:i] objectForKey:@"data"];

           

           CGContextShowTextAtPoint(ctx, kOffsetX + j * kStepX, kGraphBottom - 5, [theText cStringUsingEncoding:NSUTF8StringEncoding], [theText length]);

           j++;

       }

   }

   

   

   float * data = (float *) malloc(numberOfRecords * sizeof(float));

   for (int i = 0; i < numberOfRecords; i++)

   {

       data[i] = ((float) reps[i] / (float) maxReps);

   }

   

   CGContextSetLineWidth(ctx, 2.0);

   CGContextSetStrokeColorWithColor(ctx, [[UIColor colorWithRed:1.0 green:0.5 blue:0 alpha:1.0] CGColor]);

   

   int maxGraphHeight = kGraphHeight - kOffsetY*2;

   

   //lines

   CGContextBeginPath(ctx);

   CGContextMoveToPoint(ctx, kOffsetX, kGraphHeight - kOffsetY - maxGraphHeight * data[0]);

   for (int i = 1; i < numberOfRecords; i++)

   {

       CGContextAddLineToPoint(ctx, kOffsetX + i * kStepX, kGraphHeight - kOffsetY - maxGraphHeight * data[i]);

   }

   CGContextDrawPath(ctx, kCGPathStroke);

   

   //gradient fill

   CGGradientRef gradient;

   CGColorSpaceRef colorspace;

   size_t num_locations = 2;

   CGFloat locations[2] = {0.0, 1.0};

   CGFloat components[8] = {1.0, 0.5, 0.0, 0.2,  // Start color

       1.0, 0.5, 0.0, 1.0}; // End color

   colorspace = CGColorSpaceCreateDeviceRGB();

   gradient = CGGradientCreateWithColorComponents(colorspace, components, locations, num_locations);

   CGPoint startPoint, endPoint;

   startPoint.x = kOffsetX;

   startPoint.y = kGraphHeight;

   endPoint.x = kOffsetX;

   endPoint.y = kOffsetY;

   

   CGContextSetFillColorWithColor(ctx, [[UIColor colorWithRed:1.0 green:0.5 blue:0 alpha:0.5] CGColor]);

   CGContextBeginPath(ctx);

   CGContextMoveToPoint(ctx, kOffsetX, kGraphHeight);

   CGContextAddLineToPoint(ctx, kOffsetX, kGraphHeight - kOffsetY - maxGraphHeight * data[0]);

   for (int i = 1; i < numberOfRecords; i++)

   {

       CGContextAddLineToPoint(ctx, kOffsetX + i * kStepX, kGraphHeight - kOffsetY - maxGraphHeight * data[i]);

   }

   CGContextAddLineToPoint(ctx, kOffsetX + (numberOfRecords - 1) * kStepX, kGraphHeight);

   CGContextClosePath(ctx);

   //CGContextDrawPath(ctx, kCGPathFill);

   CGContextSaveGState(ctx);

   CGContextClip(ctx);

   CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint, 0);

   CGContextRestoreGState(ctx);

   CGColorSpaceRelease(colorspace);

   CGGradientRelease(gradient);

   

   

   //circles

   CGContextSetFillColorWithColor(ctx, [[UIColor colorWithRed:1.0 green:0.5 blue:0 alpha:1.0] CGColor]);

   for (int i = 0; i < numberOfRecords; i++)

   {

       float x = kOffsetX + i * kStepX;

       float y = kGraphHeight - kOffsetY - maxGraphHeight * data[i];

       CGRect rect = CGRectMake(x - kCircleRadius, y - kCircleRadius, 2 * kCircleRadius, 2 * kCircleRadius);

       CGContextAddEllipseInRect(ctx, rect);

   }

   CGContextDrawPath(ctx, kCGPathFillStroke);

   

   CGContextSetTextMatrix(ctx, CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, 0.0));

   CGContextSelectFont(ctx, "Helvetica", 16, kCGEncodingMacRoman);

   CGContextSetTextDrawingMode(ctx, kCGTextFill);

   CGContextSetFillColorWithColor(ctx, [[UIColor colorWithRed:0 green:0 blue:0 alpha:1.0] CGColor]);

   for (int i = 0; i < numberOfRecords; i++)

   {

       NSString *theText = [NSString stringWithFormat:@"%i", reps[i]];

       CGContextShowTextAtPoint(ctx, kOffsetX + i * kStepX + 3, kGraphHeight - kOffsetY - maxGraphHeight * data[i], [theText cStringUsingEncoding:NSUTF8StringEncoding], [theText length]);

   }

   

   free (reps);

   free (data);

}

- (void)drawRect:(CGRect)rect

{

   numberOfRecords = 0;

   

   NSString * exerciseID = [[NSUserDefaults standardUserDefaults] objectForKey:@"GotExerciseID"];

   NSString * userID = [[NSUserDefaults standardUserDefaults] objectForKey:@"GotUserID"];

   NSString * userName = [[NSUserDefaults standardUserDefaults] objectForKey:@"GotUserName"];

   

   NSDictionary *tweets;

   int num = rand();

   

   NSString * request = [NSString stringWithFormat:@"http://sportdiary.net/api/getchartdata_ios_counts.php?rand=%i&id=%@&caption_id=%@", num, userID, exerciseID];

   

   NSData* data = [NSData dataWithContentsOfURL:

                   [NSURL URLWithString: request]];

   

   NSError* error;

   if ( data )  // Will only get here if there's data

   {

       tweets = [NSJSONSerialization JSONObjectWithData:data

                                                options:kNilOptions

                                                  error:&error];

       

       if ([tweets objectForKey:@"count"] != nil)

       {

           NSString * count = [tweets objectForKey:@"count"];

           numberOfRecords = [count intValue];

       }

       

       

   }

   

   

   CGContextRef context = UIGraphicsGetCurrentContext();

   

   /* Background of Graph

    UIImage *image = [UIImage imageNamed:@"background.png"];

    CGRect imageRect = CGRectMake(0, 0, image.size.width, image.size.height);

    CGContextDrawImage(context, imageRect, image.CGImage);

    

    */

   

   CGContextSetLineWidth(context, 0.6);

   CGContextSetStrokeColorWithColor(context, [[UIColor lightGrayColor] CGColor]);

   CGFloat dash[] = {2.0, 2.0};

   CGContextSetLineDash(context, 0.0, dash, 2);

   

   // How many lines?

   int howMany;

   int graphWidth;

   if (numberOfRecords < 8)

   {

       howMany = kDefaultGraphWidth / kStepX;

       graphWidth = kDefaultGraphWidth;

   }

   else

   {

       howMany = numberOfRecords;

       graphWidth = numberOfRecords * kStepX;

   }

   

   // Here the lines go

   for (int i = 0; i < howMany; i++)

   {

       CGContextMoveToPoint(context, kOffsetX + i * kStepX, kGraphTop);

       CGContextAddLineToPoint(context, kOffsetX + i * kStepX, kGraphBottom);

   }

   

   int howManyHorizontal = (kGraphBottom - kGraphTop - kOffsetY) / kStepY;

   for (int i = 0; i <= howManyHorizontal; i++)

   {

       CGContextMoveToPoint(context, kOffsetX, kGraphBottom - kOffsetY - i * kStepY);

       CGContextAddLineToPoint(context, graphWidth, kGraphBottom - kOffsetY - i * kStepY);

   }

   

   CGContextStrokePath(context);

   CGContextSetLineDash(context, 0, NULL, 0); // Remove the dash

   

   if (numberOfRecords > 0)

       [self drawLineGraphWithContext:context];

   else

   {

       // Drawing text

       CGContextSelectFont(context, "Helvetica", 18, kCGEncodingMacRoman);

       CGContextSetTextDrawingMode(context, kCGTextFill);

       CGContextSetFillColorWithColor(context, [[UIColor colorWithRed:0 green:0 blue:0 alpha:1.0] CGColor]);

       CGContextSetTextMatrix (context, CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, 0.0));

       NSString *theText = [NSString stringWithFormat:@"No added sets yet", userName];

       CGContextShowTextAtPoint(context, 85, 120, [theText cStringUsingEncoding:NSUTF8StringEncoding], [theText length]);

   }

}

@end


 

А также другие работы, которые могут Вас заинтересовать

9195. Цели, содержание и структура непрерывного образования 16.62 KB
  Цели, содержание и структура непрерывного образования Образование - процесс и результат усвоения человеком систематизированных знаний, умений и навыков, определенный уровень интеллектуального и эмоционального развития формирование мировоззрения...
9196. Эмоционально-волевая сфера личности 27.6 KB
  Эмоционально-волевая сфера личности Структура конспекта: Понятие чувства и эмоции. Их физиологические основы Формы эмоций и высших чувства Понятие воля. Его физиологические основы и основные характеристики Структура вол...
9197. Усі уроки української літератури 5 клас 3.75 MB
  ПЕРЕДМОВА Чи люблять, чи хочуть діти вчитися? Чи цікаво їм на уроках? Яких уроків вони чекають? Якими задоволені? Звичайно, на ці та аналогіч­ ні питання однозначно відповісти важко. Проте зрозуміло: сірі, одно­ манітні, нецікаві уроки викличуть не ...
9198. Усі уроки української мови 8 клас 7.98 MB
  Усі уроки української мови у 8 класі розроблено відповідно до програми для загальноосвітніх навчальних закладів Українська мова. 5—12 класи (Г. Т. Шелехова, В. І. Тихоша, А. М. Корольчук, В. І. Но- восьолова, Я. І. Остаф; за ред Л. В. Скуратівського).
9199. ТЕОРІЯ ЙМОВІРНОСТЕЙ 325.36 KB
  ТЕМА 19. ТЕОРІЯ ЙМОВІРНОСТЕЙ Теорія ймовірностей - математична наука, яка вивчає закономірності випадкових явищ. Фундаментальними поняттями теорії ймовірностей є випадкова подія та випадковий експеримент (випробування). Випробування (випадковий...
9200. Урок географии в 8 классе Реки России 46.5 KB
  Урок географии в 8 классе Реки России География России. Природа и население. Книга первая. Под редакцией А. И. Алексеева. Учитель географии МОБУ Иссадская основная общеобразовательная школа Волховского муниципального района Румянцева Любовь Вас...
9201. Цитология - наука о клетке 86 KB
  Цитология - наука о клетке. Основные положения клеточной теории (2.1.1). Краткие сведения из истории изучения клетки (2.1.2).Прокариоты и эукариоты (2.1.3) Цели: Познакомить учащихся с проблемами цитологии и её методами. Обобщить и ...
9202. Химическая организация клетки. Углеводы, липиды 122.5 KB
  Химическая организация клетки. Углеводы, липиды. Неорганические химические элементы и вещества в клетке, их роль. Органические вещества клетки и живых организмов. Углеводы и липиды. Цели: Углубить знания о химическом с...
9203. Белки, аминокислоты. Нуклеиновые кислоты 675 KB
  Белки, аминокислоты. Нуклеиновые кислоты. Структура белков, функции белков в клетке, аминокислоты. Нуклеиновые кислоты. Тип урока - изучение нового материала. Цели: Рассмотреть особенности строения белковых молекул, познакомиться с функциями белков...