From c1a04820696661119845181ed4f4f0a63e932517 Mon Sep 17 00:00:00 2001 From: AdamMiltonBarker Date: Thu, 22 Oct 2020 00:04:29 +0200 Subject: [PATCH 01/22] 2.0.0 SQL --- Scripts/Installation/SQL.sql | 1455 +++++++++++++++++++++++----------- 1 file changed, 1003 insertions(+), 452 deletions(-) diff --git a/Scripts/Installation/SQL.sql b/Scripts/Installation/SQL.sql index 7072e96..6fa4ecb 100644 --- a/Scripts/Installation/SQL.sql +++ b/Scripts/Installation/SQL.sql @@ -3,35 +3,107 @@ -- https://www.phpmyadmin.net/ -- -- Host: localhost:3306 --- Generation Time: Oct 10, 2020 at 12:02 PM +-- Generation Time: Oct 22, 2020 at 12:01 AM -- Server version: 5.7.31-0ubuntu0.18.04.1 --- PHP Version: 7.2.24-0ubuntu0.18.04.6 +-- PHP Version: 7.2.24-0ubuntu0.18.04.7 -SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; -SET time_zone = "+00:00"; +SET SQL_MODE += "NO_AUTO_VALUE_ON_ZERO"; +SET time_zone += "+00:00"; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `amqpp` +-- + +CREATE TABLE `amqpp` +( + `id` int +(11) NOT NULL, + `uid` int +(11) NOT NULL, + `permission` varchar +(255) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `amqpu` +-- + +CREATE TABLE `amqpu` +( + `id` int +(11) NOT NULL, + `username` varchar +(255) NOT NULL, + `pw` varchar +(255) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- -- --- Database: `hiasssl` +-- Table structure for table `amqpvh` -- +CREATE TABLE `amqpvh` +( + `id` int +(11) NOT NULL, + `uid` int +(11) NOT NULL, + `vhost` varchar +(255) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + -- -------------------------------------------------------- -- --- Table structure for table `beds` +-- Table structure for table `amqpvhr` -- -CREATE TABLE `beds` ( - `id` int(11) NOT NULL, - `lid` int(11) NOT NULL DEFAULT '0', - `zid` int(11) NOT NULL DEFAULT '0', - `did` int(11) NOT NULL DEFAULT '0', - `bcaddress` varchar(255) NOT NULL, - `ip` varchar(255) NOT NULL DEFAULT 'NA', - `mac` varchar(255) NOT NULL DEFAULT 'NA', - `lt` varchar(255) NOT NULL DEFAULT '', - `lg` varchar(255) NOT NULL DEFAULT '', - `gpstime` int(11) NOT NULL DEFAULT '0', - `created` int(11) NOT NULL DEFAULT '0' +CREATE TABLE `amqpvhr` +( + `id` int +(11) NOT NULL, + `uid` int +(11) NOT NULL, + `vhost` varchar +(255) NOT NULL, + `rtype` varchar +(255) NOT NULL, + `rname` varchar +(255) NOT NULL, + `permission` varchar +(255) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `amqpvhrt` +-- + +CREATE TABLE `amqpvhrt` +( + `id` int +(11) NOT NULL, + `uid` int +(11) NOT NULL, + `vhost` varchar +(255) NOT NULL, + `rtype` varchar +(255) NOT NULL, + `rname` varchar +(255) NOT NULL, + `permission` varchar +(255) NOT NULL, + `rkey` varchar +(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- -------------------------------------------------------- @@ -40,11 +112,16 @@ CREATE TABLE `beds` ( -- Table structure for table `blockchain` -- -CREATE TABLE `blockchain` ( - `id` int(11) NOT NULL, - `dc` int(11) NOT NULL DEFAULT '0', - `pc` int(11) NOT NULL DEFAULT '0', - `ic` int(11) NOT NULL DEFAULT '0' +CREATE TABLE `blockchain` +( + `id` int +(11) NOT NULL, + `dc` int +(11) NOT NULL DEFAULT '0', + `pc` int +(11) NOT NULL DEFAULT '0', + `ic` int +(11) NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- -------------------------------------------------------- @@ -53,123 +130,338 @@ CREATE TABLE `blockchain` ( -- Table structure for table `blocked` -- -CREATE TABLE `blocked` ( - `id` int(11) NOT NULL, - `ipv6` varchar(255) NOT NULL, - `banned` int(11) NOT NULL +CREATE TABLE `blocked` +( + `id` int +(11) NOT NULL, + `ipv6` varchar +(255) NOT NULL, + `banned` int +(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- -------------------------------------------------------- -- --- Table structure for table `contracts` +-- Table structure for table `cbApplicationCats` -- -CREATE TABLE `contracts` ( - `id` int(11) NOT NULL, - `name` varchar(255) NOT NULL, - `contract` varchar(255) NOT NULL, - `acc` varchar(255) NOT NULL, - `abi` json NOT NULL, - `txn` text NOT NULL, - `uid` int(11) NOT NULL, - `time` int(11) NOT NULL +CREATE TABLE `cbApplicationCats` +( + `id` int +(11) NOT NULL, + `category` varchar +(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; +-- +-- Dumping data for table `cbApplicationCats` +-- + +INSERT INTO `cbApplicationCats` (` +id`, +`category +`) VALUES +(1, 'System'), +(2, 'IoT Agent'); + -- -------------------------------------------------------- -- --- Table structure for table `covid19data` +-- Table structure for table `cbDeviceCats` -- -CREATE TABLE `covid19data` ( - `id` int(11) NOT NULL, - `country` varchar(255) NOT NULL, - `province` varchar(255) NOT NULL, - `lat` varchar(255) NOT NULL, - `lng` varchar(255) NOT NULL, - `confirmed` int(11) NOT NULL DEFAULT '0', - `deaths` int(11) NOT NULL DEFAULT '0', - `recovered` int(11) NOT NULL DEFAULT '0', - `active` int(11) NOT NULL DEFAULT '0', - `file` varchar(255) NOT NULL, - `date` datetime NOT NULL, - `timeadded` int(11) NOT NULL +CREATE TABLE `cbDeviceCats` +( + `id` int +(11) NOT NULL, + `category` varchar +(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; +-- +-- Dumping data for table `cbDeviceCats` +-- + +INSERT INTO `cbDeviceCats` (` +id`, +`category +`) VALUES +(1, 'Camera'), +(4, 'Scanner'), +(5, 'Mixed Reality'), +(6, 'Virtual Reality'), +(7, 'Server'), +(8, 'TassAI'), +(9, 'GeniSysAI'), +(10, 'EMAR'), +(11, 'AMLClassifier'), +(12, 'ALLClassifier'), +(13, 'COVIDClassifier'), +(14, 'SkinCancerClassifier'); + -- -------------------------------------------------------- -- --- Table structure for table `covid19pulls` +-- Table structure for table `cbPatientsCats` -- -CREATE TABLE `covid19pulls` ( - `id` int(11) NOT NULL, - `pulldate` date NOT NULL, - `datefrom` date NOT NULL, - `dateto` date NOT NULL, - `rows` int(11) NOT NULL +CREATE TABLE `cbPatientsCats` +( + `id` int +(11) NOT NULL, + `category` varchar +(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; +-- +-- Dumping data for table `cbPatientsCats` +-- + +INSERT INTO `cbPatientsCats` (` +id`, +`category +`) VALUES +(1, 'Primary Care'), +(2, 'Specialty Care'), +(3, 'Emergency Care'), +(4, 'Urgent Care'), +(5, 'Long-term Care'), +(6, 'Hospice Care'); + -- -------------------------------------------------------- -- --- Table structure for table `emar` +-- Table structure for table `cbProtocols` -- -CREATE TABLE `emar` ( - `id` int(11) NOT NULL, - `name` varchar(255) NOT NULL, - `uid` int(11) NOT NULL DEFAULT '0', - `lid` int(11) NOT NULL DEFAULT '0', - `zid` int(11) NOT NULL DEFAULT '0', - `did` int(11) NOT NULL DEFAULT '0', - `aid` int(11) NOT NULL DEFAULT '0', - `mid` int(11) NOT NULL DEFAULT '0', - `ip` varchar(255) NOT NULL, - `mac` varchar(255) NOT NULL, - `sport` varchar(255) NOT NULL, - `sdir` varchar(255) NOT NULL, - `sportf` varchar(255) NOT NULL, - `sckport` varchar(255) NOT NULL +CREATE TABLE `cbProtocols` +( + `id` int +(11) NOT NULL, + `protocol` varchar +(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; +-- +-- Dumping data for table `cbProtocols` +-- + +INSERT INTO `cbProtocols` (` +id`, +`protocol +`) VALUES +(1, 'MQTT'), +(2, 'AMQP'), +(3, 'CoAP'), +(4, 'HTTP'), +(5, 'Websockets'), +(6, 'LwM2M'); + -- -------------------------------------------------------- -- --- Table structure for table `genisysai` +-- Table structure for table `cbUserCats` +-- + +CREATE TABLE `cbUserCats` +( + `id` int +(11) NOT NULL, + `category` varchar +(255) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- +-- Dumping data for table `cbUserCats` +-- + +INSERT INTO `cbUserCats` (` +id`, +`category +`) VALUES +(1, 'Management'), +(2, 'Directors'), +(3, 'Administration'), +(4, 'Supervisor'), +(5, 'Doctor'), +(6, 'Nurse'), +(7, 'Security'), +(8, 'Network Security'), +(9, 'Developer'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `cbZoneCats` +-- + +CREATE TABLE `cbZoneCats` +( + `id` int +(11) NOT NULL, + `category` varchar +(255) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + -- +-- Dumping data for table `cbZoneCats` +-- + +INSERT INTO `cbZoneCats` (` +id`, +`category +`) VALUES +(1, 'A&E'), +(2, 'Casualty'), +(3, 'Day room'), +(4, 'Delivery Room'), +(5, 'Dispensary'), +(6, 'Emergency Department'), +(7, 'Emergency Room'), +(8, 'High Dependency Unit'), +(9, 'Intensive Care Unit (ICU)'), +(10, 'Maternity Ward'), +(11, 'Nursery'), +(12, 'Operating Room'), +(13, 'Pharmacy'), +(14, 'Sick Room'), +(15, 'Surgery'), +(16, 'Office'), +(17, 'Reception'), +(18, 'Bathroom'), +(19, 'Kitchen'), +(20, 'Yard'), +(21, 'Gardens'), +(22, 'Terrace'), +(23, 'Dormitory'), +(24, 'Room'); -CREATE TABLE `genisysai` ( - `id` int(11) NOT NULL, - `type` varchar(255) NOT NULL, - `name` varchar(255) NOT NULL, - `uid` int(11) NOT NULL DEFAULT '0', - `lid` int(11) NOT NULL DEFAULT '0', - `zid` int(11) NOT NULL DEFAULT '0', - `did` int(11) NOT NULL DEFAULT '0', - `aid` int(11) NOT NULL DEFAULT '0', - `mid` int(11) NOT NULL DEFAULT '0', - `ip` varchar(255) NOT NULL DEFAULT '', - `mac` varchar(255) NOT NULL DEFAULT '', - `sport` varchar(255) NOT NULL DEFAULT '', - `sportf` varchar(255) NOT NULL DEFAULT '', - `sckport` varchar(255) NOT NULL DEFAULT '', - `strdir` varchar(255) NOT NULL +-- -------------------------------------------------------- + +-- +-- Table structure for table `contextbroker` +-- + +CREATE TABLE `contextbroker` +( + `id` int +(11) NOT NULL, + `hdsiv` varchar +(50) NOT NULL, + `url` varchar +(50) NOT NULL, + `local_ip` varchar +(255) NOT NULL, + `about_url` varchar +(255) NOT NULL, + `entities_url` varchar +(255) NOT NULL, + `types_url` varchar +(255) NOT NULL, + `subscriptions_url` varchar +(255) NOT NULL, + `registrations_url` varchar +(255) NOT NULL, + `agents_url` varchar +(255) NOT NULL, + `commands_url` varchar +(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- -------------------------------------------------------- -- --- Table structure for table `genisysainlu` +-- Table structure for table `contracts` -- -CREATE TABLE `genisysainlu` ( - `id` int(11) NOT NULL, - `lid` int(11) NOT NULL DEFAULT '0', - `zid` int(11) NOT NULL DEFAULT '0', - `did` int(11) NOT NULL DEFAULT '0', - `apidir` varchar(255) NOT NULL +CREATE TABLE `contracts` +( + `id` int +(11) NOT NULL, + `name` varchar +(255) NOT NULL, + `contract` varchar +(255) NOT NULL, + `acc` varchar +(255) NOT NULL, + `abi` json NOT NULL, + `txn` text NOT NULL, + `uid` int +(11) NOT NULL, + `time` int +(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `covid19data` +-- + +CREATE TABLE `covid19data` +( + `id` int +(11) NOT NULL, + `country` varchar +(255) NOT NULL, + `province` varchar +(255) NOT NULL, + `lat` varchar +(255) NOT NULL, + `lng` varchar +(255) NOT NULL, + `confirmed` int +(11) NOT NULL DEFAULT '0', + `deaths` int +(11) NOT NULL DEFAULT '0', + `recovered` int +(11) NOT NULL DEFAULT '0', + `active` int +(11) NOT NULL DEFAULT '0', + `file` varchar +(255) NOT NULL, + `date` datetime NOT NULL, + `timeadded` int +(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `covid19pulls` +-- + +CREATE TABLE `covid19pulls` +( + `id` int +(11) NOT NULL, + `pulldate` date NOT NULL, + `datefrom` date NOT NULL, + `dateto` date NOT NULL, + `rows` int +(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `genisysai` +-- + +CREATE TABLE `genisysai` +( + `id` int +(11) NOT NULL, + `uid` int +(11) NOT NULL DEFAULT '0', + `isGeniSys` tinyint +(4) NOT NULL DEFAULT '0', + `device` varchar +(255) NOT NULL, + `chat` text NOT NULL, + `timestamp` int +(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- -------------------------------------------------------- @@ -178,21 +470,36 @@ CREATE TABLE `genisysainlu` ( -- Table structure for table `history` -- -CREATE TABLE `history` ( - `id` int(11) NOT NULL, - `uid` int(11) NOT NULL DEFAULT '0', - `action` varchar(255) NOT NULL, - `hash` int(11) NOT NULL DEFAULT '0', - `tuid` int(11) NOT NULL DEFAULT '0', - `tlid` int(11) NOT NULL DEFAULT '0', - `tzid` int(11) NOT NULL DEFAULT '0', - `tdid` int(11) NOT NULL DEFAULT '0', - `tsid` int(11) NOT NULL DEFAULT '0', - `taid` int(11) NOT NULL DEFAULT '0', - `tcid` int(11) NOT NULL DEFAULT '0', - `tpid` int(11) NOT NULL DEFAULT '0', - `tbid` int(11) NOT NULL DEFAULT '0', - `time` int(11) NOT NULL +CREATE TABLE `history` +( + `id` int +(11) NOT NULL, + `uid` int +(11) NOT NULL DEFAULT '0', + `action` varchar +(255) NOT NULL, + `hash` int +(11) NOT NULL DEFAULT '0', + `tuid` int +(11) NOT NULL DEFAULT '0', + `tlid` int +(11) NOT NULL DEFAULT '0', + `tzid` int +(11) NOT NULL DEFAULT '0', + `tdid` int +(11) NOT NULL DEFAULT '0', + `tsid` int +(11) NOT NULL DEFAULT '0', + `taid` int +(11) NOT NULL DEFAULT '0', + `tcid` int +(11) NOT NULL DEFAULT '0', + `tpid` int +(11) NOT NULL DEFAULT '0', + `tbid` int +(11) NOT NULL DEFAULT '0', + `time` int +(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- -------------------------------------------------------- @@ -201,12 +508,16 @@ CREATE TABLE `history` ( -- Table structure for table `logins` -- -CREATE TABLE `logins` ( - `id` int(11) NOT NULL, - `ipv6` varchar(255) NOT NULL, +CREATE TABLE `logins` +( + `id` int +(11) NOT NULL, + `ipv6` varchar +(255) NOT NULL, `browser` text NOT NULL, `language` text NOT NULL, - `time` varchar(255) NOT NULL + `time` varchar +(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- -------------------------------------------------------- @@ -215,12 +526,30 @@ CREATE TABLE `logins` ( -- Table structure for table `loginsf` -- -CREATE TABLE `loginsf` ( - `id` int(11) NOT NULL, - `ipv6` varchar(255) NOT NULL, +CREATE TABLE `loginsf` +( + `id` int +(11) NOT NULL, + `ipv6` varchar +(255) NOT NULL, `browser` text NOT NULL, `language` text NOT NULL, - `time` varchar(255) NOT NULL + `time` varchar +(255) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `models` +-- + +CREATE TABLE `models` +( + `id` int +(11) NOT NULL, + `pub` varchar +(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- -------------------------------------------------------- @@ -229,30 +558,12 @@ CREATE TABLE `loginsf` ( -- Table structure for table `mqtta` -- -CREATE TABLE `mqtta` ( - `id` int(11) NOT NULL, - `admin` int(11) NOT NULL DEFAULT '0', - `iotJumpWay` int(11) NOT NULL DEFAULT '0', - `cancelled` int(11) NOT NULL DEFAULT '0', - `status` varchar(255) NOT NULL DEFAULT 'OFFLINE', - `uid` int(11) NOT NULL DEFAULT '0', - `pid` int(11) NOT NULL DEFAULT '0', - `lid` int(11) NOT NULL, - `mqttu` varchar(255) NOT NULL DEFAULT '', - `mqttp` varchar(255) NOT NULL DEFAULT '', - `apub` varchar(255) NOT NULL DEFAULT '', - `aprv` varchar(255) NOT NULL DEFAULT '', - `bcaddress` varchar(255) NOT NULL, - `name` varchar(255) NOT NULL DEFAULT '', - `lt` varchar(255) NOT NULL DEFAULT '', - `lg` varchar(255) NOT NULL DEFAULT '', - `ip` varchar(255) NOT NULL DEFAULT '', - `mac` varchar(255) NOT NULL DEFAULT '', - `time` int(11) NOT NULL DEFAULT '0', - `cpu` decimal(10,2) NOT NULL DEFAULT '0.00', - `mem` decimal(10,2) NOT NULL DEFAULT '0.00', - `hdd` decimal(10,2) NOT NULL DEFAULT '0.00', - `tempr` decimal(10,2) NOT NULL DEFAULT '0.00' +CREATE TABLE `mqtta` +( + `id` int +(11) NOT NULL, + `apub` varchar +(255) NOT NULL DEFAULT '' ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- -------------------------------------------------------- @@ -261,15 +572,12 @@ CREATE TABLE `mqtta` ( -- Table structure for table `mqttl` -- -CREATE TABLE `mqttl` ( - `id` int(11) NOT NULL, - `name` varchar(255) NOT NULL DEFAULT '', - `ip` varchar(255) NOT NULL DEFAULT '', - `mac` varchar(255) NOT NULL DEFAULT '', - `zones` int(11) NOT NULL DEFAULT '0', - `devices` int(11) NOT NULL DEFAULT '0', - `apps` int(11) NOT NULL DEFAULT '0', - `time` int(11) NOT NULL +CREATE TABLE `mqttl` +( + `id` int +(11) NOT NULL, + `pub` varchar +(255) NOT NULL DEFAULT '' ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- -------------------------------------------------------- @@ -278,29 +586,12 @@ CREATE TABLE `mqttl` ( -- Table structure for table `mqttld` -- -CREATE TABLE `mqttld` ( - `id` int(11) NOT NULL, - `status` varchar(255) NOT NULL DEFAULT 'OFFLINE', - `uid` int(11) NOT NULL DEFAULT '0', - `lid` int(11) NOT NULL, - `zid` int(11) NOT NULL DEFAULT '0', - `bid` int(11) NOT NULL DEFAULT '0', - `mqttu` varchar(255) NOT NULL DEFAULT '', - `mqttp` varchar(255) NOT NULL DEFAULT '', - `bcaddress` varchar(255) NOT NULL DEFAULT '', - `bcpw` varchar(255) NOT NULL DEFAULT '', - `apub` varchar(255) NOT NULL DEFAULT '', - `aprv` varchar(255) NOT NULL DEFAULT '', - `name` varchar(255) NOT NULL DEFAULT '', - `ip` varchar(255) NOT NULL DEFAULT '', - `mac` varchar(255) NOT NULL DEFAULT '', - `lt` varchar(255) NOT NULL DEFAULT '', - `lg` varchar(255) NOT NULL DEFAULT '', - `time` int(11) NOT NULL DEFAULT '0', - `cpu` decimal(10,2) NOT NULL DEFAULT '0.00', - `mem` decimal(10,2) NOT NULL DEFAULT '0.00', - `hdd` decimal(10,2) NOT NULL DEFAULT '0.00', - `tempr` decimal(10,2) NOT NULL DEFAULT '0.00' +CREATE TABLE `mqttld` +( + `id` int +(11) NOT NULL, + `apub` varchar +(255) NOT NULL DEFAULT '' ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- -------------------------------------------------------- @@ -309,12 +600,12 @@ CREATE TABLE `mqttld` ( -- Table structure for table `mqttlz` -- -CREATE TABLE `mqttlz` ( - `id` int(11) NOT NULL, - `uid` int(11) NOT NULL DEFAULT '0', - `lid` int(11) NOT NULL DEFAULT '0', - `zn` varchar(255) NOT NULL, - `time` int(11) NOT NULL +CREATE TABLE `mqttlz` +( + `id` int +(11) NOT NULL, + `pub` varchar +(255) NOT NULL DEFAULT '' ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- -------------------------------------------------------- @@ -323,18 +614,30 @@ CREATE TABLE `mqttlz` ( -- Table structure for table `mqttu` -- -CREATE TABLE `mqttu` ( - `id` int(11) NOT NULL, - `uid` int(11) NOT NULL DEFAULT '0', - `lid` int(11) NOT NULL DEFAULT '0', - `zid` int(11) NOT NULL DEFAULT '0', - `did` int(11) NOT NULL DEFAULT '0', - `aid` int(11) NOT NULL DEFAULT '0', - `pid` int(11) NOT NULL DEFAULT '0', - `bid` int(11) NOT NULL DEFAULT '0', - `uname` varchar(255) NOT NULL, - `pw` varchar(255) NOT NULL, - `super` tinyint(4) NOT NULL DEFAULT '0' +CREATE TABLE `mqttu` +( + `id` int +(11) NOT NULL, + `uid` int +(11) NOT NULL DEFAULT '0', + `lid` int +(11) NOT NULL DEFAULT '0', + `zid` int +(11) NOT NULL DEFAULT '0', + `did` int +(11) NOT NULL DEFAULT '0', + `aid` int +(11) NOT NULL DEFAULT '0', + `pid` int +(11) NOT NULL DEFAULT '0', + `bid` int +(11) NOT NULL DEFAULT '0', + `uname` varchar +(255) NOT NULL, + `pw` varchar +(255) NOT NULL, + `super` tinyint +(4) NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- -------------------------------------------------------- @@ -343,18 +646,30 @@ CREATE TABLE `mqttu` ( -- Table structure for table `mqttua` -- -CREATE TABLE `mqttua` ( - `id` int(11) NOT NULL, - `uid` int(11) NOT NULL DEFAULT '0', - `lid` int(11) NOT NULL DEFAULT '0', - `zid` int(11) NOT NULL DEFAULT '0', - `did` int(11) NOT NULL DEFAULT '0', - `aid` int(11) NOT NULL DEFAULT '0', - `pid` int(11) NOT NULL DEFAULT '0', - `bid` int(11) NOT NULL DEFAULT '0', - `username` varchar(255) NOT NULL, - `topic` varchar(255) NOT NULL, - `rw` tinyint(4) NOT NULL DEFAULT '0' +CREATE TABLE `mqttua` +( + `id` int +(11) NOT NULL, + `uid` int +(11) NOT NULL DEFAULT '0', + `lid` int +(11) NOT NULL DEFAULT '0', + `zid` int +(11) NOT NULL DEFAULT '0', + `did` int +(11) NOT NULL DEFAULT '0', + `aid` int +(11) NOT NULL DEFAULT '0', + `pid` int +(11) NOT NULL DEFAULT '0', + `bid` int +(11) NOT NULL DEFAULT '0', + `username` varchar +(255) NOT NULL, + `topic` varchar +(255) NOT NULL, + `rw` tinyint +(4) NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- -------------------------------------------------------- @@ -363,25 +678,20 @@ CREATE TABLE `mqttua` ( -- Table structure for table `patients` -- -CREATE TABLE `patients` ( - `id` int(11) NOT NULL, - `uid` int(11) NOT NULL DEFAULT '0', - `lid` int(11) NOT NULL DEFAULT '0', - `aid` int(11) NOT NULL DEFAULT '0', - `active` tinyint(1) NOT NULL DEFAULT '0', - `admitted` tinyint(1) NOT NULL DEFAULT '0', - `discharged` tinyint(1) NOT NULL DEFAULT '0', - `name` varchar(255) NOT NULL DEFAULT '', - `email` varchar(255) NOT NULL DEFAULT '', - `username` varchar(255) NOT NULL DEFAULT '', - `password` varchar(255) NOT NULL DEFAULT '', - `bcaddress` varchar(255) NOT NULL, - `bcpass` varchar(255) NOT NULL, - `pic` varchar(255) NOT NULL DEFAULT 'default.png', - `lt` varchar(255) NOT NULL DEFAULT '', - `lg` varchar(255) NOT NULL DEFAULT '', - `gpstime` int(11) NOT NULL DEFAULT '0', - `created` int(11) NOT NULL +CREATE TABLE `patients` +( + `id` int +(11) NOT NULL, + `pub` varchar +(255) NOT NULL DEFAULT '', + `username` varchar +(255) NOT NULL DEFAULT '', + `password` varchar +(255) NOT NULL DEFAULT '', + `bcaddress` varchar +(255) NOT NULL DEFAULT '', + `bcpass` varchar +(255) NOT NULL DEFAULT '' ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- -------------------------------------------------------- @@ -390,68 +700,101 @@ CREATE TABLE `patients` ( -- Table structure for table `sensors` -- -CREATE TABLE `sensors` ( - `id` int(11) NOT NULL, - `name` varchar(255) NOT NULL, - `type` varchar(255) NOT NULL, - `hasAction` tinyint(4) NOT NULL DEFAULT '0', - `hasCommand` tinyint(4) NOT NULL DEFAULT '0', - `image` varchar(255) NOT NULL DEFAULT 'default.png' +CREATE TABLE `sensors` +( + `id` int +(11) NOT NULL, + `pub` varchar +(255) NOT NULL DEFAULT '', + `name` varchar +(255) NOT NULL, + `type` varchar +(255) NOT NULL, + `hasAction` tinyint +(4) NOT NULL DEFAULT '0', + `hasCommand` tinyint +(4) NOT NULL DEFAULT '0', + `image` varchar +(255) NOT NULL DEFAULT 'default.png' ) ENGINE=InnoDB DEFAULT CHARSET=latin1; --- --- Dumping data for table `sensors` --- - -INSERT INTO `sensors` (`id`, `name`, `type`, `hasAction`, `hasCommand`, `image`) VALUES -(1, 'LED', 'Actuator', 0, 1, 'led.jpg'), -(2, 'Button', 'Actuator', 1, 0, 'button.jpg'), -(3, 'Reed Switch', 'Sensor', 1, 0, 'reed-switch.jpg'), -(4, 'Motion Sensor', 'Sensor', 1, 0, 'motion-sensor.jpg'), -(5, 'Light Sensor', 'Sensor', 1, 0, 'light-sensor.jpg'), -(6, 'Sound Sensor', 'Sensor', 1, 0, 'sound-sensor.jpg'), -(7, 'Camera', 'Sensor', 1, 0, 'cctv.jpg'), -(8, 'Buzzer', 'Actuator', 0, 1, 'buzzer.jpg'), -(9, 'Moisture Sensor', 'Sensor', 1, 0, 'moisture-sensor.jpg'), -(10, 'Rain Sensor', 'Sensor', 1, 0, 'RainSensor.jpg'), -(11, 'Water Level Sensor', 'Sensor', 1, 0, 'WaterLevelSensor.jpg'), -(12, 'Temperature Sensor', 'Sensor', 1, 0, 'temperature.jpg'), -(13, 'Servo', 'Actuator', 0, 1, 'servo.jpg'), -(14, 'Servo Controller', 'Actuator', 1, 0, 'servoController.jpg'), -(15, 'Relay', 'Actuator', 0, 1, 'relay.jpg'), -(16, 'NFC Scanner', 'Sensor', 1, 0, 'NFCScanner.jpg'), -(17, 'LCD Keypad (4 Buttons)', 'Sensor', 1, 1, 'LCD-KeyPad-4.jpg'), -(18, 'Virtual Controller', 'Sensor', 1, 0, 'virtual-controller.png'); - -- -------------------------------------------------------- -- -- Table structure for table `settings` -- -CREATE TABLE `settings` ( - `id` int(11) NOT NULL, - `aid` int(11) NOT NULL, - `version` varchar(50) NOT NULL, - `phpmyadmin` varchar(255) NOT NULL, - `recaptcha` varchar(255) NOT NULL, - `recaptchas` varchar(255) NOT NULL, - `gmaps` varchar(255) NOT NULL, - `lt` varchar(255) NOT NULL, - `lg` varchar(255) NOT NULL, - `meta_title` varchar(255) NOT NULL, +CREATE TABLE `settings` +( + `id` int +(11) NOT NULL, + `aid` int +(11) NOT NULL, + `version` varchar +(50) NOT NULL, + `phpmyadmin` varchar +(255) NOT NULL, + `recaptcha` varchar +(255) NOT NULL, + `recaptchas` varchar +(255) NOT NULL, + `gmaps` varchar +(255) NOT NULL, + `lt` varchar +(255) NOT NULL, + `lg` varchar +(255) NOT NULL, + `meta_title` varchar +(255) NOT NULL, `meta_description` text NOT NULL, `meta_keywords` text NOT NULL, - `domainString` varchar(255) NOT NULL, - `ip` varchar(255) NOT NULL + `domainString` varchar +(255) NOT NULL, + `ip` varchar +(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- -- Dumping data for table `settings` -- -INSERT INTO `settings` (`id`, `aid`, `version`, `phpmyadmin`, `recaptcha`, `recaptchas`, `gmaps`, `lt`, `lg`, `meta_title`, `meta_description`, `meta_keywords`, `domainString`, `ip`) VALUES -(1, 0, '1.0.2', 'phpmyadmin', '', '', '', '', '', 'HIAS Hospital Intelligent Automation System', 'Open-source Hospital Intelligent Automation System & Hospital Information/Management System. A locally hosted web/IoT server and proxy for managing a network of open-source, modular, intelligent devices, robotics and applications.', '', '', ''); +INSERT INTO `settings` (` +id`, +`aid`, +`version`, +`phpmyadmin`, +`recaptcha`, +`recaptchas +`, `gmaps`, `lt`, `lg`, `meta_title`, `meta_description`, `meta_keywords`, `domainString`, `ip`) VALUES +(1, 2, '1.1.0', 'phpmyadmin', 'SVFqS0JncU5oT0pvOUdobGtNK2dHbXk3RHRodG5CdDUveFBoLzZUSUI2aUNYZHc2ak5LV0t5aUlyZjdGSE1nODo6rA64JT8kzGizTwxC4bwOkg==', 'ZU55dm5XRVBFU1ZDUFN0cVhCbkQvbFJ3VHlqZzIyVkErVmpLL2lqUzFoU2ZYM3NGY3NmaDBOTGxSWmhXd2pwVTo6/ZA5JMyOInW6+UUDtT2YWA==', 'MW9QM1dJZDErZjM4VUozcjdLVTA1VFVzbFEyVzJtcXZ5a0V4R2tMdzcrUlpSOFZ1WUZ0bWFZc2dtQUZoOHFhODo6FWJlzBbWBdGXrCX6g0U5mw==', 'SFMxajJLbTlxcXlKYnYzRFFKeENRQT09OjpKZgXKvnmt2K0soA8yhFKh', 'WkxuNTdlY2lyVDZubDIzeVk3WHZzUT09Ojp13grLr8AkZxvVbEEuJcQq', 'HIAS Hospital Intelligent Automation System', 'Open-source Hospital Intelligent Automation System & Hospital Information/Management System. A locally hosted web/IoT server and proxy for managing a network of open-source, modular, intelligent devices, robotics and applications.', '', 'eWNpT2NZSTYvMFlkRTNpRVZDUTlUb3NHcjlrdXlRazVhZ0ljU2NETHk4WUlDWVpFWnJkRjNsTWJwOFZLNjNiTjo60O5b1mEC6KzoqAdeVtU32A==', 'cTJPRDNZdy93TXZnM1MwYjZNd2UwZ1hvMmxjcTVPWStPMVBIUW94S2VyZz06OnKd1+InHYK7DXhuOgu1T5o='); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `tassai` +-- + +CREATE TABLE `tassai` +( + `id` int +(11) NOT NULL, + `pub` varchar +(255) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `things` +-- + +CREATE TABLE `things` +( + `id` int +(11) NOT NULL, + `pub` varchar +(255) NOT NULL DEFAULT '' +) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- -------------------------------------------------------- @@ -459,17 +802,29 @@ INSERT INTO `settings` (`id`, `aid`, `version`, `phpmyadmin`, `recaptcha`, `reca -- Table structure for table `transactions` -- -CREATE TABLE `transactions` ( - `id` int(11) NOT NULL, - `uid` int(11) NOT NULL DEFAULT '0', - `did` int(11) NOT NULL DEFAULT '0', - `aid` int(11) NOT NULL DEFAULT '0', - `cid` int(11) NOT NULL DEFAULT '0', - `pid` int(11) NOT NULL DEFAULT '0', - `bid` int(11) NOT NULL DEFAULT '0', - `action` varchar(255) NOT NULL, +CREATE TABLE `transactions` +( + `id` int +(11) NOT NULL, + `uid` int +(11) NOT NULL DEFAULT '0', + `tuid` int +(11) NOT NULL DEFAULT '0', + `did` int +(11) NOT NULL DEFAULT '0', + `aid` int +(11) NOT NULL DEFAULT '0', + `cid` int +(11) NOT NULL DEFAULT '0', + `pid` int +(11) NOT NULL DEFAULT '0', + `bid` int +(11) NOT NULL DEFAULT '0', + `action` varchar +(255) NOT NULL, `hash` text NOT NULL, - `time` int(11) NOT NULL + `time` int +(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- -------------------------------------------------------- @@ -478,28 +833,22 @@ CREATE TABLE `transactions` ( -- Table structure for table `users` -- -CREATE TABLE `users` ( - `id` int(11) NOT NULL, - `cancelled` int(11) NOT NULL DEFAULT '0', - `lid` int(11) NOT NULL DEFAULT '0', - `aid` int(11) NOT NULL DEFAULT '0', - `admin` int(11) NOT NULL DEFAULT '0', - `patients` int(11) NOT NULL DEFAULT '0', - `name` varchar(255) NOT NULL, - `username` varchar(255) NOT NULL, - `bcaddress` varchar(255) NOT NULL, - `bcpw` varchar(255) NOT NULL, - `email` varchar(255) NOT NULL, - `password` varchar(255) NOT NULL, - `nfc` varchar(255) NOT NULL, - `pic` varchar(255) NOT NULL DEFAULT 'default.png', - `lt` varchar(255) NOT NULL DEFAULT '', - `lg` varchar(255) NOT NULL DEFAULT '', - `gpstime` int(11) NOT NULL DEFAULT '0', - `cz` int(11) NOT NULL DEFAULT '0', - `czt` int(11) NOT NULL DEFAULT '0', - `welcomed` int(11) NOT NULL DEFAULT '0', - `created` int(11) NOT NULL DEFAULT '0' +CREATE TABLE `users` +( + `id` int +(11) NOT NULL, + `pub` varchar +(255) NOT NULL DEFAULT '', + `aid` int +(11) NOT NULL DEFAULT '0', + `username` varchar +(255) NOT NULL, + `bcaddress` varchar +(255) NOT NULL, + `bcpw` varchar +(255) NOT NULL, + `password` varchar +(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- @@ -507,339 +856,541 @@ CREATE TABLE `users` ( -- -- --- Indexes for table `beds` +-- Indexes for table `amqpp` +-- +ALTER TABLE `amqpp` +ADD PRIMARY KEY +(`id`), +ADD KEY `uid` +(`uid`); + +-- +-- Indexes for table `amqpu` -- -ALTER TABLE `beds` - ADD PRIMARY KEY (`id`), - ADD KEY `gpstime` (`gpstime`), - ADD KEY `lid` (`lid`), - ADD KEY `zid` (`zid`), - ADD KEY `did` (`did`); +ALTER TABLE `amqpu` +ADD PRIMARY KEY +(`id`); + +-- +-- Indexes for table `amqpvh` +-- +ALTER TABLE `amqpvh` +ADD PRIMARY KEY +(`id`), +ADD KEY `uid` +(`uid`); + +-- +-- Indexes for table `amqpvhr` +-- +ALTER TABLE `amqpvhr` +ADD PRIMARY KEY +(`id`), +ADD KEY `uid` +(`uid`); + +-- +-- Indexes for table `amqpvhrt` +-- +ALTER TABLE `amqpvhrt` +ADD PRIMARY KEY +(`id`), +ADD KEY `uid` +(`uid`); -- -- Indexes for table `blockchain` -- ALTER TABLE `blockchain` - ADD PRIMARY KEY (`id`), - ADD KEY `dc` (`dc`); +ADD PRIMARY KEY +(`id`), +ADD KEY `dc` +(`dc`); -- -- Indexes for table `blocked` -- ALTER TABLE `blocked` - ADD PRIMARY KEY (`id`), - ADD KEY `banned` (`banned`); +ADD PRIMARY KEY +(`id`), +ADD KEY `banned` +(`banned`); + +-- +-- Indexes for table `cbApplicationCats` +-- +ALTER TABLE `cbApplicationCats` +ADD PRIMARY KEY +(`id`); + +-- +-- Indexes for table `cbDeviceCats` +-- +ALTER TABLE `cbDeviceCats` +ADD PRIMARY KEY +(`id`); + +-- +-- Indexes for table `cbPatientsCats` +-- +ALTER TABLE `cbPatientsCats` +ADD PRIMARY KEY +(`id`); + +-- +-- Indexes for table `cbProtocols` +-- +ALTER TABLE `cbProtocols` +ADD PRIMARY KEY +(`id`); + +-- +-- Indexes for table `cbUserCats` +-- +ALTER TABLE `cbUserCats` +ADD PRIMARY KEY +(`id`); + +-- +-- Indexes for table `cbZoneCats` +-- +ALTER TABLE `cbZoneCats` +ADD PRIMARY KEY +(`id`); + +-- +-- Indexes for table `contextbroker` +-- +ALTER TABLE `contextbroker` +ADD PRIMARY KEY +(`id`); -- -- Indexes for table `contracts` -- ALTER TABLE `contracts` - ADD PRIMARY KEY (`id`); +ADD PRIMARY KEY +(`id`); -- -- Indexes for table `covid19data` -- ALTER TABLE `covid19data` - ADD PRIMARY KEY (`id`), - ADD KEY `country` (`country`), - ADD KEY `province` (`province`), - ADD KEY `timeadded` (`timeadded`); +ADD PRIMARY KEY +(`id`), +ADD KEY `country` +(`country`), +ADD KEY `province` +(`province`), +ADD KEY `timeadded` +(`timeadded`); -- -- Indexes for table `covid19pulls` -- ALTER TABLE `covid19pulls` - ADD PRIMARY KEY (`id`); - --- --- Indexes for table `emar` --- -ALTER TABLE `emar` - ADD PRIMARY KEY (`id`), - ADD KEY `uid` (`uid`), - ADD KEY `lid` (`lid`), - ADD KEY `zid` (`zid`), - ADD KEY `did` (`did`), - ADD KEY `aid` (`aid`), - ADD KEY `mid` (`mid`); +ADD PRIMARY KEY +(`id`); -- -- Indexes for table `genisysai` -- ALTER TABLE `genisysai` - ADD PRIMARY KEY (`id`), - ADD KEY `uid` (`uid`), - ADD KEY `lid` (`lid`), - ADD KEY `zid` (`zid`), - ADD KEY `did` (`did`), - ADD KEY `aid` (`aid`), - ADD KEY `mid` (`mid`); - --- --- Indexes for table `genisysainlu` --- -ALTER TABLE `genisysainlu` - ADD PRIMARY KEY (`id`), - ADD KEY `lid` (`lid`), - ADD KEY `zid` (`zid`), - ADD KEY `did` (`did`); +ADD PRIMARY KEY +(`id`), +ADD KEY `isGeniSys` +(`isGeniSys`), +ADD KEY `uid` +(`uid`); -- -- Indexes for table `history` -- ALTER TABLE `history` - ADD PRIMARY KEY (`id`), - ADD KEY `uid` (`uid`), - ADD KEY `tuid` (`tuid`), - ADD KEY `tzne` (`tzid`), - ADD KEY `tlid` (`tlid`), - ADD KEY `tdid` (`tdid`), - ADD KEY `sid` (`tsid`), - ADD KEY `taid` (`taid`); +ADD PRIMARY KEY +(`id`), +ADD KEY `uid` +(`uid`), +ADD KEY `tuid` +(`tuid`), +ADD KEY `tzne` +(`tzid`), +ADD KEY `tlid` +(`tlid`), +ADD KEY `tdid` +(`tdid`), +ADD KEY `sid` +(`tsid`), +ADD KEY `taid` +(`taid`); -- -- Indexes for table `logins` -- ALTER TABLE `logins` - ADD PRIMARY KEY (`id`); +ADD PRIMARY KEY +(`id`); -- -- Indexes for table `loginsf` -- ALTER TABLE `loginsf` - ADD PRIMARY KEY (`id`); +ADD PRIMARY KEY +(`id`); + +-- +-- Indexes for table `models` +-- +ALTER TABLE `models` +ADD PRIMARY KEY +(`id`); -- -- Indexes for table `mqtta` -- ALTER TABLE `mqtta` - ADD PRIMARY KEY (`id`), - ADD KEY `time` (`time`), - ADD KEY `lid` (`lid`), - ADD KEY `mqttu` (`mqttu`), - ADD KEY `pid` (`pid`), - ADD KEY `admin` (`admin`), - ADD KEY `cancelled` (`cancelled`); +ADD PRIMARY KEY +(`id`); -- -- Indexes for table `mqttl` -- ALTER TABLE `mqttl` - ADD PRIMARY KEY (`id`), - ADD KEY `time` (`time`), - ADD KEY `apps` (`apps`), - ADD KEY `name` (`name`), - ADD KEY `devices` (`devices`), - ADD KEY `zones` (`zones`); +ADD PRIMARY KEY +(`id`); -- -- Indexes for table `mqttld` -- ALTER TABLE `mqttld` - ADD PRIMARY KEY (`id`), - ADD KEY `time` (`time`), - ADD KEY `lid` (`lid`), - ADD KEY `mqttu` (`mqttu`), - ADD KEY `zid` (`zid`), - ADD KEY `uid` (`uid`), - ADD KEY `bid` (`bid`); +ADD PRIMARY KEY +(`id`); -- -- Indexes for table `mqttlz` -- ALTER TABLE `mqttlz` - ADD PRIMARY KEY (`id`), - ADD KEY `uid` (`uid`), - ADD KEY `lid` (`lid`), - ADD KEY `time` (`time`); +ADD PRIMARY KEY +(`id`); -- -- Indexes for table `mqttu` -- ALTER TABLE `mqttu` - ADD PRIMARY KEY (`id`), - ADD KEY `lid` (`lid`), - ADD KEY `znid` (`zid`), - ADD KEY `did` (`did`), - ADD KEY `aid` (`aid`), - ADD KEY `pid` (`pid`), - ADD KEY `bid` (`bid`); +ADD PRIMARY KEY +(`id`), +ADD KEY `lid` +(`lid`), +ADD KEY `znid` +(`zid`), +ADD KEY `did` +(`did`), +ADD KEY `aid` +(`aid`), +ADD KEY `pid` +(`pid`), +ADD KEY `bid` +(`bid`); -- -- Indexes for table `mqttua` -- ALTER TABLE `mqttua` - ADD PRIMARY KEY (`id`), - ADD KEY `lid` (`lid`), - ADD KEY `zid` (`zid`), - ADD KEY `did` (`did`), - ADD KEY `aid` (`aid`), - ADD KEY `uid` (`uid`), - ADD KEY `lid_2` (`lid`), - ADD KEY `zid_2` (`zid`), - ADD KEY `did_2` (`did`), - ADD KEY `aid_2` (`aid`), - ADD KEY `pid` (`pid`), - ADD KEY `bid` (`bid`); +ADD PRIMARY KEY +(`id`), +ADD KEY `lid` +(`lid`), +ADD KEY `zid` +(`zid`), +ADD KEY `did` +(`did`), +ADD KEY `aid` +(`aid`), +ADD KEY `uid` +(`uid`), +ADD KEY `lid_2` +(`lid`), +ADD KEY `zid_2` +(`zid`), +ADD KEY `did_2` +(`did`), +ADD KEY `aid_2` +(`aid`), +ADD KEY `pid` +(`pid`), +ADD KEY `bid` +(`bid`); -- -- Indexes for table `patients` -- ALTER TABLE `patients` - ADD PRIMARY KEY (`id`), - ADD KEY `gpstime` (`gpstime`); +ADD PRIMARY KEY +(`id`); -- -- Indexes for table `sensors` -- ALTER TABLE `sensors` - ADD PRIMARY KEY (`id`), - ADD KEY `hasAction` (`hasAction`), - ADD KEY `hasCommand` (`hasCommand`); +ADD PRIMARY KEY +(`id`), +ADD KEY `hasAction` +(`hasAction`), +ADD KEY `hasCommand` +(`hasCommand`); -- -- Indexes for table `settings` -- ALTER TABLE `settings` - ADD PRIMARY KEY (`id`), - ADD KEY `did` (`aid`); +ADD PRIMARY KEY +(`id`), +ADD KEY `did` +(`aid`); + +-- +-- Indexes for table `tassai` +-- +ALTER TABLE `tassai` +ADD PRIMARY KEY +(`id`); + +-- +-- Indexes for table `things` +-- +ALTER TABLE `things` +ADD PRIMARY KEY +(`id`); -- -- Indexes for table `transactions` -- ALTER TABLE `transactions` - ADD PRIMARY KEY (`id`), - ADD KEY `did` (`did`), - ADD KEY `aid` (`aid`); +ADD PRIMARY KEY +(`id`), +ADD KEY `did` +(`did`), +ADD KEY `aid` +(`aid`); -- -- Indexes for table `users` -- ALTER TABLE `users` - ADD PRIMARY KEY (`id`), - ADD KEY `admin` (`admin`), - ADD KEY `gpstime` (`gpstime`), - ADD KEY `cz` (`cz`), - ADD KEY `czt` (`czt`), - ADD KEY `welcomed` (`welcomed`), - ADD KEY `cancelled` (`cancelled`); +ADD PRIMARY KEY +(`id`); -- -- AUTO_INCREMENT for dumped tables -- -- --- AUTO_INCREMENT for table `beds` +-- AUTO_INCREMENT for table `amqpp` +-- +ALTER TABLE `amqpp` + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; +-- +-- AUTO_INCREMENT for table `amqpu` +-- +ALTER TABLE `amqpu` + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; +-- +-- AUTO_INCREMENT for table `amqpvh` +-- +ALTER TABLE `amqpvh` + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; +-- +-- AUTO_INCREMENT for table `amqpvhr` +-- +ALTER TABLE `amqpvhr` + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -ALTER TABLE `beds` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; +-- AUTO_INCREMENT for table `amqpvhrt` +-- +ALTER TABLE `amqpvhrt` + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `blockchain` -- ALTER TABLE `blockchain` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `blocked` -- ALTER TABLE `blocked` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; +-- +-- AUTO_INCREMENT for table `cbApplicationCats` +-- +ALTER TABLE `cbApplicationCats` + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; +-- +-- AUTO_INCREMENT for table `cbDeviceCats` +-- +ALTER TABLE `cbDeviceCats` + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; +-- +-- AUTO_INCREMENT for table `cbPatientsCats` +-- +ALTER TABLE `cbPatientsCats` + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; +-- +-- AUTO_INCREMENT for table `cbProtocols` +-- +ALTER TABLE `cbProtocols` + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; +-- +-- AUTO_INCREMENT for table `cbUserCats` +-- +ALTER TABLE `cbUserCats` + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; +-- +-- AUTO_INCREMENT for table `cbZoneCats` +-- +ALTER TABLE `cbZoneCats` + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; +-- +-- AUTO_INCREMENT for table `contextbroker` +-- +ALTER TABLE `contextbroker` + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `contracts` -- ALTER TABLE `contracts` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `covid19data` -- ALTER TABLE `covid19data` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `covid19pulls` -- ALTER TABLE `covid19pulls` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; --- --- AUTO_INCREMENT for table `emar` --- -ALTER TABLE `emar` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `genisysai` -- ALTER TABLE `genisysai` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; --- --- AUTO_INCREMENT for table `genisysainlu` --- -ALTER TABLE `genisysainlu` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `history` -- ALTER TABLE `history` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `logins` -- ALTER TABLE `logins` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `loginsf` -- ALTER TABLE `loginsf` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; +-- +-- AUTO_INCREMENT for table `models` +-- +ALTER TABLE `models` + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `mqtta` -- ALTER TABLE `mqtta` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `mqttl` -- ALTER TABLE `mqttl` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `mqttld` -- ALTER TABLE `mqttld` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `mqttlz` -- ALTER TABLE `mqttlz` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `mqttu` -- ALTER TABLE `mqttu` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `mqttua` -- ALTER TABLE `mqttua` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `patients` -- ALTER TABLE `patients` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `sensors` -- ALTER TABLE `sensors` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=19; + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `settings` -- ALTER TABLE `settings` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; +-- +-- AUTO_INCREMENT for table `tassai` +-- +ALTER TABLE `tassai` + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; +-- +-- AUTO_INCREMENT for table `things` +-- +ALTER TABLE `things` + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `transactions` -- ALTER TABLE `transactions` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT for table `users` -- ALTER TABLE `users` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; \ No newline at end of file + MODIFY `id` int +(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6; \ No newline at end of file From f400378582711dd76283284b782bc2bed2eee695 Mon Sep 17 00:00:00 2001 From: AdamMiltonBarker Date: Thu, 22 Oct 2020 00:40:54 +0200 Subject: [PATCH 02/22] 2.0.0 Configuration File --- confs.json | 109 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 64 insertions(+), 45 deletions(-) diff --git a/confs.json b/confs.json index 3311ff4..96a248e 100644 --- a/confs.json +++ b/confs.json @@ -1,33 +1,74 @@ { "iotJumpWay": { - "channels": { - "commands": "Commands" + "cert": "/fserver/certs/fullchain.pem", + "host": "YourHiasDomainName", + "ip": "YourHiasIP", + "ipinfo": "YourHiasIpInfoKey", + "ContextBroker": { + "version": "v1", + "address": "ContextBroker/v1", + "iport": 8883, + "port": 3524, + "lid": "YourIotJumpWayLocationID", + "aid": "YourContextBrokerApplicationID", + "an": "YourContextBrokerApplicationName", + "un": "YourContextBrokerApplicationMqttUsername", + "pw": "YourContextBrokerApplicationMqttPassword" }, - "host": "YourSubdomain.YourDomain.YourTLD", - "port": 8883, - "ip": "localhost", - "ipinfo": "YourIpInfoApiKey", - "lid": YourLocationID, - "zid": YourZoneID, - "did": YourDeviceId, - "dn": "YourDeviceName", - "gun": "YourDeviceMqttUsername", - "gpw": "YourDeviceMqttPass", - "paid": YourIotJumpWayApplicationID, - "pan": "YourIotJumpWayApplicationName", - "pun": "YourIotJumpWayMqttUser", - "ppw": "YourIotJumpWayMqttPass", - "mdb": "YourMongoDatabaseName", - "mdbu": "YourMongoDatabaseUser", - "mdbp": "YourMongoDatabasePass", - "dbname": "YourMySqlDatabaseName", - "dbuser": "YourMySqlDatabaseUser", - "dbpass": "YourMySqlDatabasePass" + "databases": { + "mongo": { + "ip": "localhost", + "db": "YourMongoDatabaseName", + "dbu": "YourMongoDatabaseUsername", + "dbp": "YourMongoDatabasePassword" + } + }, + "AMQP": { + "vhost": "iotJumpWay", + "exchange": "Core", + "identifier": "YourAmqpApplicationIdentifier", + "un": "YourAmqpApplicationUsername", + "pw": "YourAmqpApplicationPasword", + "northPort": 8254, + "port": 8282 + }, + "MQTT": { + "port": 8883, + "Agent": { + "lid": "YourIotJumpWayLocationID", + "aid": "YourIotJumpWayApplicationID", + "an": "YourIotJumpWayApplicationName", + "un": "YourIotJumpWayApplicationUsername", + "pw": "YourIotJumpWayApplicationPassword", + "northPort": 3256, + "identifier": "YourIotJumpWayApplicationIdentifier", + "auth": "YourIotJumpWayApplicationAuthKey" + }, + "TassAI": { + "allowed": [".jpg", ".JPG", ".png", ".PNG"], + "data": "/fserver/models/TassAI/Data/Security/", + "detection": "/fserver/models/TassAI/face-detection-retail-0004.xml", + "dlib": "/fserver/models/TassAI/shape_predictor_68_face_landmarks.dat", + "dlibr": "/fserver/models/TassAI/dlib_face_recognition_resnet_model_v1.dat", + "reidentification": "/fserver/models/TassAI/face-reidentification-retail-0095.xml", + "landmarks": "/fserver/models/TassAI/landmarks-regression-retail-0009.xml", + "runas": "CPU", + "test": "Test/", + "threshold": 0.6, + "port": 8080, + "lid": "YourIotJumpWayLocationID", + "zid": "YourTassAIZoneID", + "did": "YourTassAIDeviceID", + "dn": "Server Security API", + "un": "YourTassAIMqttUsername", + "pw": "YourTassAIMqttPassword" + } + } }, "ethereum": { "authAbi": [], "authContract": "YourAuthContractAddress", - "bchost": "https://YourSubdomain.YourDomain.YourTLD/Blockchain/API/", + "bchost": "https://YourHiasDomainName/Blockchain/API/", "iotAbi": [], "iotContract": "YourIotContractAddress", "haddress": "YourHiasBlockhainUserAddress", @@ -38,27 +79,5 @@ "pass": "YourIotUserPass", "patientsAbi": [], "patientsContract": "YourPatientsContractAddress" - }, - "TassAI": { - "core": { - "allowed": [ - ".jpg", - ".JPG", - ".png", - ".PNG" - ] - }, - "ip": "YourCameraApiIP", - "data": "/fserver/models/TassAI/Data/Security/", - "detection": "/fserver/models/TassAI/face-detection-retail-0004.xml", - "dlib": "/fserver/models/TassAI/shape_predictor_68_face_landmarks.dat", - "dlibr": "/fserver/models/TassAI/dlib_face_recognition_resnet_model_v1.dat", - "reidentification": "/fserver/models/TassAI/face-reidentification-retail-0095.xml", - "landmarks": "/fserver/models/TassAI/landmarks-regression-retail-0009.xml", - "runas": "CPU", - "test": "Test/", - "port": 8080, - "threshold": 0.6, - "vid": 0 } } \ No newline at end of file From 60bfa52ca2e32bb4dcdf2ec57f2665836e86daa6 Mon Sep 17 00:00:00 2001 From: AdamMiltonBarker Date: Thu, 22 Oct 2020 00:45:38 +0200 Subject: [PATCH 03/22] 2.0.0 NGINX Configuration --- Root/etc/nginx/sites-available/default | 208 +++++++++++++++---------- 1 file changed, 128 insertions(+), 80 deletions(-) diff --git a/Root/etc/nginx/sites-available/default b/Root/etc/nginx/sites-available/default index 66d5b7d..b632050 100644 --- a/Root/etc/nginx/sites-available/default +++ b/Root/etc/nginx/sites-available/default @@ -1,90 +1,138 @@ server { - server_name YourSubdomain.YourDomain.TLD; - root /fserver/var/www/html; + root /fserver/var/www/html; + server_name YourHiasDomainName; client_max_body_size 100M; - location /Blockchain/API/ { - auth_basic "Restricted"; - auth_basic_user_file /etc/nginx/security/htpasswd; - proxy_pass http://HiasServerIp:8545/; - } - - location /Security/API/ { - auth_basic "Restricted"; - auth_basic_user_file /etc/nginx/security/htpasswd; - proxy_pass http://HiasServerIp:8080/; - } - - location / { - rewrite ^/Blockchain/Contracts/Create$ /Blockchain/CCreate.php last; - rewrite ^/Blockchain/Contracts/([0-9]+)/Transaction/([0-9]+)$ /Blockchain/Transaction.php?contract=$1&transaction=$2 last; - rewrite ^/Blockchain/Contracts/Contract/([0-9]+)$ /Blockchain/Contract.php?contract=$1 last; - rewrite ^/Data-Analysis/COVID-19/(.+)/([A-Za-z]+)/([A-Za-z]+)$ /Data-Analysis/COVID-19/index.php?country=$1&period=$2&stat=$3 last; - rewrite ^/Robotics/EMAR/([0-9]+)$ /Robotics/EMAR/Device.php?emar=$1 last; - rewrite ^/iotJumpWay/Sensors/([0-9]+)$ /iotJumpWay/Sensor.php?sensor=$1 last; - rewrite ^/iotJumpWay/Sensors/Update$ /iotJumpWay/SensorUpdate.php last; - rewrite ^/iotJumpWay/Sensors/Upload$ /iotJumpWay/SensorUpload.php last; - rewrite ^/iotJumpWay/Sensors/Create$ /iotJumpWay/CreateSensor.php last; - rewrite ^/iotJumpWay/Devices/Create$ /iotJumpWay/CreateDevice.php last; - rewrite ^/iotJumpWay/([0-9]+)/Zones/([0-9]+)/Devices/([0-9]+)/Transaction/([0-9]+)$ /iotJumpWay/DeviceTransaction.php?location=$1&zone=$2&device=$3&transaction=$4 last; - rewrite ^/iotJumpWay/([0-9]+)/Zones/([0-9]+)/Devices/([0-9]+)$ /iotJumpWay/Device.php?location=$1&zone=$2&device=$3 last; - rewrite ^/iotJumpWay/Zones/Create$ /iotJumpWay/CreateZone.php last; - rewrite ^/iotJumpWay/([0-9]+)/Zones/([0-9]+)$ /iotJumpWay/Zone.php?location=$1&zone=$2 last; - rewrite ^/iotJumpWay/Applications/Create$ /iotJumpWay/CreateApp.php last; - rewrite ^/iotJumpWay/([0-9]+)/Applications/([0-9]+)/Transaction/([0-9]+)$ /iotJumpWay/ApplicationTransaction.php?location=$1&application=$2&transaction=$3 last; - rewrite ^/iotJumpWay/([0-9]+)/Applications/([0-9]+)$ /iotJumpWay/Application.php?location=$1&application=$2 last; - rewrite ^/Hospital/Patients/([0-9]+)$ /Hospital/Patients/Patient.php?patient=$1 last; - rewrite ^/Hospital/Patients/([0-9]+)/Transaction/([0-9]+)$ /Hospital/Patients/Transaction.php?patient=$1&transaction=$2 last; - rewrite ^/Hospital/Beds/([0-9]+)/$ /Hospital/Beds/Bed.php?bed=$1 last; - rewrite ^/Hospital/Beds/([0-9]+)/Transaction/([0-9]+)$ /Hospital/Beds/Transaction.php?bed=$1&transaction=$2 last; - rewrite ^/Hospital/Staff/API/Applications/NLU/([A-Za-z]+)$ /Hospital/Staff/API/Applications/NLU/index.php?params=$1 last; - rewrite ^/Hospital/Staff/API/Applications/User/([A-Za-z]+)$ /Hospital/Staff/API/Applications/User/index.php?params=$1 last; - rewrite ^/Hospital/Staff/([0-9]+)$ /Hospital/Staff/Staff.php?staff=$1 last; - rewrite ^/Security/GeniSysAI/([0-9]+)$ /Security/GeniSysAI/Device.php?genisysai=$1 last; - rewrite ^/GeniSysAI/([0-9]+)$ /GeniSysAI/Device.php?genisysai=$1 last; - try_files $uri $uri.html $uri/ @extensionless-php; - index index.php index.html index.htm index.nginx-debian.html; - } - - location ~ \.php$ { - include fastcgi_params; - include snippets/fastcgi-php.conf; - fastcgi_pass unix:/run/php/php7.2-fpm.sock; - } - - - location @extensionless-php { - rewrite ^(.*)$ $1.php last; - } - - location ~ /\.ht { - deny all; - } - - listen [::]:443 ssl ipv6only=on; # managed by Certbot - listen 443 ssl; # managed by Certbot - ssl_certificate /etc/letsencrypt/live/YourSubdomain.YourDomain.TLD/fullchain.pem; # managed by Certbot - ssl_certificate_key /etc/letsencrypt/live/YourSubdomain.YourDomain.TLD/privkey.pem; # managed by Certbot - include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot - ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot -} -server { - listen 80; - server_name ""; - return 444; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + send_timeout 600; + + location /ContextBroker/v1/about { + auth_basic "Restricted"; + auth_basic_user_file /etc/nginx/security/htpasswd; + proxy_pass http://HiasServerIp:3524/about; + } + + location /ContextBroker/v1/entities { + auth_basic "Restricted"; + auth_basic_user_file /etc/nginx/security/htpasswd; + proxy_pass http://HiasServerIp:3524/entities; + } + + location /ContextBroker/v1/entities/(.*)$ { + auth_basic "Restricted"; + auth_basic_user_file /etc/nginx/security/htpasswd; + proxy_pass http://HiasServerIp:3524/entities/$1$is_args$args; + } + + location /ContextBroker/v1/entities/(.*)/attrs$ { + auth_basic "Restricted"; + auth_basic_user_file /etc/nginx/security/htpasswd; + proxy_pass http://HiasServerIp:3524/entities/$1/attrs$is_args$args; + } + + location /ContextBroker/v1/agents { + auth_basic "Restricted"; + auth_basic_user_file /etc/nginx/security/htpasswd; + proxy_pass http://HiasServerIp:3524/agents; + } + + location /ContextBroker/v1/agents/(.*)$ { + auth_basic "Restricted"; + auth_basic_user_file /etc/nginx/security/htpasswd; + proxy_pass http://HiasServerIp:3524/agents/$1$is_args$args; + } + + location /ContextBroker/v1/agents/(.*)/attrs$ { + auth_basic "Restricted"; + auth_basic_user_file /etc/nginx/security/htpasswd; + proxy_pass http://HiasServerIp:3524/agents/$1/attrs$is_args$args; + } + + location ~* ^/TassAI/API/(.*)$ { + auth_basic "Restricted"; + auth_basic_user_file /etc/nginx/security/htpasswd; + proxy_pass http://HiasServerIp:8080/$1; + } + + location ~ \.php$ { + include fastcgi_params; + include snippets/fastcgi-php.conf; + fastcgi_pass unix:/run/php/php7.2-fpm.sock; + fastcgi_read_timeout 300; + } + + location @extensionless-php { + rewrite ^(.*)$ $1.php last; + } + + location ~ /\.ht { + deny all; + } + + location / { + rewrite ^/AI/TassAI/([0-9]+)$ /AI/TassAI/Device.php?device=$1 last; + rewrite ^/AI/AML/([0-9]+)/Classify$ /AI/AML/Classify.php?device=$1 last; + rewrite ^/AI/AML/([0-9]+)$ /AI/AML/Device.php?device=$1 last; + rewrite ^/AI/ALL/([0-9]+)/Classify$ /AI/ALL/Classify.php?device=$1 last; + rewrite ^/AI/ALL/([0-9]+)$ /AI/ALL/Device.php?device=$1 last; + rewrite ^/AI/COVID/([0-9]+)/Classify$ /AI/COVID/Classify.php?device=$1 last; + rewrite ^/AI/COVID/([0-9]+)$ /AI/COVID/Device.php?device=$1 last; + rewrite ^/AI/Skin/([0-9]+)/Classify$ /AI/Skin/Classify.php?device=$1 last; + rewrite ^/AI/Skin/([0-9]+)$ /AI/Skin/Device.php?device=$1 last; + rewrite ^/AI/GeniSysAI/([0-9]+)$ /AI/GeniSysAI/Device.php?device=$1 last; + rewrite ^/AI/Model/([0-9]+)$ /AI/Model.php?model=$1 last; + rewrite ^/Blockchain/Contracts/Create$ /Blockchain/CCreate.php last; + rewrite ^/Blockchain/Contracts/([0-9]+)/Transaction/([0-9]+)$ /Blockchain/Transaction.php?contract=$1&transaction=$2 last; + rewrite ^/Blockchain/Contracts/Contract/([0-9]+)$ /Blockchain/Contract.php?contract=$1 last; + rewrite ^/Data-Analysis/COVID-19/(.+)/([A-Za-z]+)/([A-Za-z]+)$ /Data-Analysis/COVID-19/index.php?country=$1&period=$2&stat=$3 last; + rewrite ^/Hospital/Patients/([0-9]+)$ /Hospital/Patients/Patient.php?patient=$1 last; + rewrite ^/Hospital/Patients/([0-9]+)/Transaction/([0-9]+)$ /Hospital/Patients/Transaction.php?patient=$1&transaction=$2 last; + rewrite ^/Hospital/Beds/([0-9]+)/$ /Hospital/Beds/Bed.php?bed=$1 last; + rewrite ^/Hospital/Beds/([0-9]+)/Transaction/([0-9]+)$ /Hospital/Beds/Transaction.php?bed=$1&transaction=$2 last; + rewrite ^/Hospital/Staff/API/Applications/NLU/([A-Za-z]+)$ /Hospital/Staff/API/Applications/NLU/index.php?params=$1 last; + rewrite ^/Hospital/Staff/API/Applications/User/([A-Za-z]+)$ /Hospital/Staff/API/Applications/User/index.php?params=$1 last; + rewrite ^/Hospital/Staff/([0-9]+)$ /Hospital/Staff/Staff.php?staff=$1 last; + rewrite ^/iotJumpWay/Things/([0-9]+)$ /iotJumpWay/Thing.php?thing=$1 last; + rewrite ^/iotJumpWay/Things/Update$ /iotJumpWay/ThingUpdate.php last; + rewrite ^/iotJumpWay/Things/Upload$ /iotJumpWay/ThingUpload.php last; + rewrite ^/iotJumpWay/Things/Create$ /iotJumpWay/CreateThing.php last; + rewrite ^/iotJumpWay/Devices/Create$ /iotJumpWay/CreateDevice.php last; + rewrite ^/iotJumpWay/ContextBroker/Agents/Create$ /iotJumpWay/ContextBroker/AgentCreate.php last; + rewrite ^/iotJumpWay/ContextBroker/Agents/Agent/([0-9]+)$ /iotJumpWay/ContextBroker/Agent.php?agent=$1 last; + rewrite ^/iotJumpWay/ContextBroker/Agents/([0-9]+)/Transaction/([0-9]+)$ /iotJumpWay/ContextBroker/Transaction.php?agent=$1&transaction=$2 last; + rewrite ^/iotJumpWay/([0-9]+)/Zones/([0-9]+)/Devices/([0-9]+)/Transaction/([0-9]+)$ /iotJumpWay/DeviceTransaction.php?location=$1&zone=$2&device=$3&transaction=$4 last; + rewrite ^/iotJumpWay/([0-9]+)/Zones/([0-9]+)/Devices/([0-9]+)$ /iotJumpWay/Device.php?location=$1&zone=$2&device=$3 last; + rewrite ^/iotJumpWay/Zones/Create$ /iotJumpWay/CreateZone.php last; + rewrite ^/iotJumpWay/([0-9]+)/Zones/([0-9]+)$ /iotJumpWay/Zone.php?location=$1&zone=$2 last; + rewrite ^/iotJumpWay/Applications/Create$ /iotJumpWay/CreateApp.php last; + rewrite ^/iotJumpWay/([0-9]+)/Applications/([0-9]+)/Transaction/([0-9]+)$ /iotJumpWay/ApplicationTransaction.php?location=$1&application=$2&transaction=$3 last; + rewrite ^/iotJumpWay/([0-9]+)/Applications/([0-9]+)$ /iotJumpWay/Application.php?location=$1&application=$2 last; + rewrite ^/Robotics/EMAR/([0-9]+)$ /Robotics/EMAR/Device.php?device=$1 last; + try_files $uri $uri.html $uri/ @extensionless-php; + index index.php index.html index.htm index.nginx-debian.html; + } + + listen [::]:443 ssl ipv6only=on; # managed by Certbot + listen 443 ssl; # managed by Certbot + ssl_certificate /etc/letsencrypt/live/YourHiasDomainName/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/YourHiasDomainName/privkey.pem; # managed by Certbot + include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot } -server { - if ($host = YourSubdomain.YourDomain.TLD){ - return 301 https://$host$request_uri; - } # managed by Certbot +server { + if ($host = YourHiasDomainName){ + return 301 https://$host$request_uri; + } # managed by Certbot - listen 80 default_server; - listen [::]:80 default_server; + listen 80 default_server; + listen [::]:80 default_server; - server_name YourSubdomain.YourDomain.TLD; - return 404; # managed by Certbot + server_name YourHiasDomainName; + return 404; # managed by Certbot -} \ No newline at end of file +} From 35ba9c3843aaa50158cb780e3f3023e95b783a1e Mon Sep 17 00:00:00 2001 From: AdamMiltonBarker Date: Thu, 22 Oct 2020 00:50:42 +0200 Subject: [PATCH 04/22] 2.0.0 Core Classes --- Classes/Blockchain.py | 55 ++++--- Classes/ContextBroker.py | 86 ++++++++++ Classes/Helpers.py | 3 +- Classes/MQTT.py | 344 +++++++++++++++++++++++++++++++++++++++ Classes/MongoDB.py | 107 +++++++++++- Classes/MySQL.py | 333 ------------------------------------- Classes/TassAI.py | 35 ++-- Classes/iotJumpWay.py | 331 ------------------------------------- 8 files changed, 580 insertions(+), 714 deletions(-) create mode 100644 Classes/ContextBroker.py create mode 100644 Classes/MQTT.py delete mode 100644 Classes/MySQL.py delete mode 100644 Classes/iotJumpWay.py diff --git a/Classes/Blockchain.py b/Classes/Blockchain.py index 570cf21..b0bd556 100644 --- a/Classes/Blockchain.py +++ b/Classes/Blockchain.py @@ -1,14 +1,16 @@ +#!/usr/bin/env python3 ###################################################################################################### # -# Organization: Peter Moss Leukemia AI Research +# Organization: Asociacion De Investigacion En Inteligencia Artificial Para La Leucemia Peter Moss # Repository: HIAS: Hospital Intelligent Automation System +# Module: Blockchain # # Author: Adam Milton-Barker (AdamMiltonBarker.com) # # Title: Blockchain Class # Description: Handles communication with the HIAS Blockchain. # License: MIT License -# Last Modified: 2020-09-20 +# Last Modified: 2020-10-18 # ###################################################################################################### @@ -21,7 +23,6 @@ from web3 import Web3 from Classes.Helpers import Helpers -from Classes.MySQL import MySQL class Blockchain(): @@ -40,17 +41,14 @@ def __init__(self): self.Helpers.logger.info("Blockchain Class initialization complete.") def startBlockchain(self): - """ Connects to MySQL database. """ + """ Connects to HIAS Blockchain. """ self.w3 = Web3(Web3.HTTPProvider(self.Helpers.confs["ethereum"]["bchost"], request_kwargs={ 'auth': HTTPBasicAuth(self.Helpers.confs["ethereum"]["user"], self.Helpers.confs["ethereum"]["pass"])})) - self.authContract = self.w3.eth.contract(self.w3.toChecksumAddress( - self.Helpers.confs["ethereum"]["authContract"]), abi=json.dumps(self.Helpers.confs["ethereum"]["authAbi"])) - self.iotContract = self.w3.eth.contract(self.w3.toChecksumAddress( - self.Helpers.confs["ethereum"]["iotContract"]), abi=json.dumps(self.Helpers.confs["ethereum"]["iotAbi"])) - self.patientsContract = self.w3.eth.contract(self.w3.toChecksumAddress( - self.Helpers.confs["ethereum"]["patientsContract"]), abi=json.dumps(self.Helpers.confs["ethereum"]["patientsAbi"])) + self.authContract = self.w3.eth.contract(self.w3.toChecksumAddress(self.Helpers.confs["ethereum"]["authContract"]), abi=json.dumps(self.Helpers.confs["ethereum"]["authAbi"])) + self.iotContract = self.w3.eth.contract(self.w3.toChecksumAddress(self.Helpers.confs["ethereum"]["iotContract"]), abi=json.dumps(self.Helpers.confs["ethereum"]["iotAbi"])) + self.patientsContract = self.w3.eth.contract(self.w3.toChecksumAddress(self.Helpers.confs["ethereum"]["patientsContract"]), abi=json.dumps(self.Helpers.confs["ethereum"]["patientsAbi"])) self.Helpers.logger.info("Blockchain connections started") def hiasAccessCheck(self, typeof, identifier): @@ -79,8 +77,8 @@ def getBalance(self, contract): return balance except: e = sys.exc_info() - self.Helpers.logger.info("Get Balance Failed!") - self.Helpers.logger.info(str(e)) + self.Helpers.logger.error("Get Balance Failed!") + self.Helpers.logger.error(str(e)) return False def hashCommand(self, data): @@ -107,8 +105,7 @@ def hashLifeData(self, data): """ Hashes the data for data integrity. """ hasher = str(data["CPU"]) + str(data["Memory"]) + str(data["Diskspace"]) + \ - str(data["Temperature"]) + \ - str(data["Latitude"]) + str(data["Longitude"]) + str(data["Temperature"]) + str(data["Latitude"]) + str(data["Longitude"]) return bcrypt.hashpw(hasher.encode(), bcrypt.gensalt()) @@ -129,15 +126,18 @@ def replenish(self, contract, to, replenish): "gas": 1000000, "value": self.w3.toWei(replenish, "ether")}) self.Helpers.logger.info("HIAS Blockchain Replenish Transaction OK! ") - self.Helpers.logger.info(tx_hash) - tx_receipt = self.w3.eth.waitForTransactionReceipt(tx_hash) - self.Helpers.logger.info("HIAS Blockchain Replenish OK!") - self.Helpers.logger.info(str(tx_receipt)) + #self.Helpers.logger.info(tx_hash) + txr = self.w3.eth.waitForTransactionReceipt(tx_hash) + if txr["status"] is 1: + self.Helpers.logger.info("HIAS Blockchain Data Hash OK!") + #self.Helpers.logger.info(str(txr)) + else: + self.Helpers.logger.error("HIAS Blockchain Data Hash KO!") return True except: e = sys.exc_info() - self.Helpers.logger.info("HIAS Blockchain Replenish Failed! ") - self.Helpers.logger.info(str(e)) + self.Helpers.logger.error("HIAS Blockchain Replenish Failed! ") + self.Helpers.logger.error(str(e)) return False def storeHash(self, dbid, hashed, at, inserter, identifier, to, typeof): @@ -148,13 +148,16 @@ def storeHash(self, dbid, hashed, at, inserter, identifier, to, typeof): "from": self.w3.toChecksumAddress(self.Helpers.confs["ethereum"]["iaddress"]), "gas": 1000000}) self.Helpers.logger.info("HIAS Blockchain Data Transaction OK!") - self.Helpers.logger.info(txh) + #self.Helpers.logger.info(txh) txr = self.w3.eth.waitForTransactionReceipt(txh) - self.Helpers.logger.info("HIAS Blockchain Data Hash OK!") - self.Helpers.logger.info(str(txr)) + if txr["status"] is 1: + self.Helpers.logger.info("HIAS Blockchain Data Hash OK!") + #self.Helpers.logger.info(str(txr)) + else: + self.Helpers.logger.error("HIAS Blockchain Data Hash KO!") except: e = sys.exc_info() - self.Helpers.logger.info("HIAS Blockchain Data Hash Failed!") - self.Helpers.logger.info(str(e)) - self.Helpers.logger.info(str(e)) + self.Helpers.logger.error("HIAS Blockchain Data Hash KO!") + self.Helpers.logger.error(str(e)) + self.Helpers.logger.error(str(e)) diff --git a/Classes/ContextBroker.py b/Classes/ContextBroker.py new file mode 100644 index 0000000..2e91dce --- /dev/null +++ b/Classes/ContextBroker.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +###################################################################################################### +# +# Organization: Peter Moss Leukemia AI Research +# Repository: HIAS: Hospital Intelligent Automation System +# +# Author: Adam Milton-Barker (AdamMiltonBarker.com) +# +# Title: HIAS Context Broker Helpers +# Description: Helper functions that allow the HIAS iotAgents to communicate with the Context +# Broker. +# License: MIT License +# Last Modified: 2020-10-18 +# +###################################################################################################### + +import json +import requests + +from Classes.Helpers import Helpers + + +class ContextBroker(): + """ HIAS Context Broker Helpers + + Helper functions that allow the HIAS iotAgents + to communicate with the Context Broker. + """ + + def __init__(self): + """ Initializes the class. """ + + self.Helpers = Helpers("ContextBroker") + self.headers = {"content-type": 'application/json'} + self.Helpers.logger.info("Context Broker initialization complete.") + + def getRequiredAttributes(self, _id, typeof): + """ Gets required attributes. """ + + if typeof is "Application": + params = "&attrs = blockchain.address, lid.value, lid.entity" + else: + params = "&attrs = blockchain.address, lid.value, lid.entity, zid.entity" + + apiUrl = "https://" + self.Helpers.confs["iotJumpWay"]["host"] + "/" + self.Helpers.confs["iotJumpWay"]["ContextBroker"]["address"] + "/entities/" + _id + "?type=" + typeof + "&attrs=blockchain.address,lid.value,lid.entity" + + response = requests.get(apiUrl, headers=self.headers, auth=( + self.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["identifier"], self.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["auth"])) + + return json.loads(response.text) + + def getNFC(self, nfc): + """ Gets required attributes. """ + + apiUrl = "https://" + self.Helpers.confs["iotJumpWay"]["host"] + "/" + \ + self.Helpers.confs["iotJumpWay"]["ContextBroker"]["address"] + \ + "/entities?type=Staff&values=nfc.value|nfc" + + response = requests.get(apiUrl, headers=self.headers, auth=( + self.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["identifier"], self.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["auth"])) + + return json.loads(response.text) + + def getNLU(self, zone): + """ Gets required attributes. """ + + apiUrl = "https://" + self.Helpers.confs["iotJumpWay"]["host"] + "/" + \ + self.Helpers.confs["iotJumpWay"]["ContextBroker"]["address"] + \ + "/entities?type=Device&category.value=GeniSysAI&values=zid.value|" + zone + ",status|ONLINE" + + response = requests.get(apiUrl, headers=self.headers, auth=( + self.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["identifier"], self.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["auth"])) + + return json.loads(response.text) + + def updateEntity(self, _id, typeof, data): + """ Updates an entity. """ + + apiUrl = "https://" + self.Helpers.confs["iotJumpWay"]["host"] + "/" + \ + self.Helpers.confs["iotJumpWay"]["ContextBroker"]["address"] + \ + "/entities/" + _id + "/attrs?type=" + typeof + + response = requests.patch(apiUrl, data=json.dumps(data), headers=self.headers, auth=( + self.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["identifier"], self.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["auth"])) + + return json.loads(response.text) diff --git a/Classes/Helpers.py b/Classes/Helpers.py index 0cdd849..8fe6662 100644 --- a/Classes/Helpers.py +++ b/Classes/Helpers.py @@ -1,7 +1,8 @@ ###################################################################################################### # -# Organization: Peter Moss Leukemia AI Research +# Organization: Asociacion De Investigacion En Inteligencia Artificial Para La Leucemia Peter Moss # Repository: HIAS: Hospital Intelligent Automation System +# Project: GeniSysAI # # Author: Adam Milton-Barker (AdamMiltonBarker.com) # Contributors: diff --git a/Classes/MQTT.py b/Classes/MQTT.py new file mode 100644 index 0000000..3cbd11f --- /dev/null +++ b/Classes/MQTT.py @@ -0,0 +1,344 @@ +#!/usr/bin/env python3 +###################################################################################################### +# +# Organization: Peter Moss Leukemia AI Research +# Repository: HIAS: Hospital Intelligent Automation System +# +# Author: Adam Milton-Barker (AdamMiltonBarker.com) +# +# Title: iotJumpWay MQTT +# Description: iotJumpWay Device & Application MQTT connection classes for HIAS. +# License: MIT License +# Last Modified: 2020-10-17 +# +###################################################################################################### + +import inspect, json, os + +import paho.mqtt.client as mqtt + +from Classes.Helpers import Helpers + +class Application(): + """ iotJumpWay Class + + The iotJumpWay Class provides the Hospital Intelligent Automation System with + it's IoT functionality. + """ + + def __init__(self, configs): + """ Initializes the class. """ + + self.Helpers = Helpers("iotJumpWay") + self.confs = configs + + self.Helpers.logger.info("Initiating Local iotJumpWay Application.") + + if self.confs['host'] == None: + raise ConfigurationException("** Host (host) property is required") + elif self.confs['port'] == None: + raise ConfigurationException("** Port (port) property is required") + elif self.confs['lid'] == None: + raise ConfigurationException("** Location ID (lid) property is required") + elif self.confs['aid'] == None: + raise ConfigurationException("** Application ID (aid) property is required") + elif self.confs['an'] == None: + raise ConfigurationException("** Application Name (an) property is required") + elif self.confs['un'] == None: + raise ConfigurationException("** MQTT UserName (un) property is required") + elif self.confs['pw'] == None: + raise ConfigurationException("** MQTT Password (pw) property is required") + + self.mqttClient = None + self.mqttTLS = "/etc/ssl/certs/DST_Root_CA_X3.pem" + self.mqttHost = self.confs['host'] + self.mqttPort = self.confs['port'] + + self.appCommandsCallback = None + self.appSensorCallback = None + self.appStatusCallback = None + self.appTriggerCallback = None + self.appLifeCallback = None + self.deviceCameraCallback = None + self.deviceCommandsCallback = None + self.deviceLifeCallback = None + self.deviceNfcCallback = None + self.deviceSensorCallback = None + self.deviceStatusCallback = None + self.deviceTriggerCallback = None + + self.Helpers.logger.info("Local iotJumpWay Application Initiated.") + + def connect(self): + + self.Helpers.logger.info("Initiating Local iotJumpWay Application Connection.") + + self.mqttClient = mqtt.Client(client_id = self.confs['an'], clean_session = True) + applicationStatusTopic = '%s/Applications/%s/Status' % (self.confs['lid'], self.confs['aid']) + self.mqttClient.will_set(applicationStatusTopic, "OFFLINE", 0, False) + self.mqttClient.tls_set(self.mqttTLS, certfile=None, keyfile=None) + self.mqttClient.on_connect = self.on_connect + self.mqttClient.on_message = self.on_message + self.mqttClient.on_publish = self.on_publish + self.mqttClient.on_subscribe = self.on_subscribe + self.mqttClient.username_pw_set(str(self.confs['un']), str(self.confs['pw'])) + self.mqttClient.connect(self.mqttHost, self.mqttPort, 10) + self.mqttClient.loop_start() + + self.Helpers.logger.info("Local iotJumpWay Application Connection Initiated.") + + def on_connect(self, client, obj, flags, rc): + + self.Helpers.logger.info("Local iotJumpWay Application Connection Successful.") + self.Helpers.logger.info("rc: " + str(rc)) + + self.appStatusPub("ONLINE") + + def appStatusPub(self, data): + + deviceStatusTopic = '%s/Applications/%s/Status' % (self.confs['lid'], self.confs['aid']) + self.mqttClient.publish(deviceStatusTopic, data) + self.Helpers.logger.info("Published to Application Status " + deviceStatusTopic) + + def on_subscribe(self, client, obj, mid, granted_qos): + + self.Helpers.logger.info("Local iotJumpWay Subscription: "+str(self.confs['an'])) + + def on_message(self, client, obj, msg): + + self.Helpers.logger.info("Local iotJumpWay Message Received") + splitTopic=msg.topic.split("/") + + if splitTopic[1]=='Applications': + if splitTopic[3]=='Status': + if self.appStatusCallback == None: + self.Helpers.logger.info("** Application Status Callback Required (appStatusCallback)") + else: + self.appStatusCallback(msg.topic,msg.payload) + elif splitTopic[3]=='Command': + if self.cameraCallback == None: + self.Helpers.logger.info("** Application Camera Callback Required (cameraCallback)") + else: + self.cameraCallback(msg.topic,msg.payload) + elif splitTopic[3]=='Life': + if self.appLifeCallback == None: + self.Helpers.logger.info("** Application Life Callback Required (appLifeCallback)") + else: + self.appLifeCallback(msg.topic,msg.payload) + elif splitTopic[1] == 'Devices': + if splitTopic[4] == 'Actuators': + if self.deviceActuatorCallback == None: + self.Helpers.logger.info("** Device Actuator Callback Required (deviceActuatorCallback)") + else: + self.deviceActuatorCallback(msg.topic, msg.payload) + elif splitTopic[4]=='Commands': + if self.deviceCommandsCallback == None: + self.Helpers.logger.info("** Device Commands Callback Required (deviceCommandsCallback)") + else: + self.deviceCommandsCallback(msg.topic,msg.payload) + elif splitTopic[4]=='Life': + if self.deviceLifeCallback == None: + self.Helpers.logger.info("** Device Life Callback Required (deviceLifeCallback)") + else: + self.deviceLifeCallback(msg.topic, msg.payload) + elif splitTopic[4]=='NFC': + if self.deviceNfcCallback == None: + self.Helpers.logger.info("** Device NFC Callback Required (deviceNfcCallback)") + else: + self.deviceNfcCallback(msg.topic, msg.payload) + elif splitTopic[4] == 'Status': + if self.deviceStatusCallback == None: + self.Helpers.logger.info("** Device Status Callback Required (deviceStatusCallback)") + else: + self.deviceStatusCallback(msg.topic, msg.payload) + elif splitTopic[4]=='Sensors': + if self.deviceSensorCallback == None: + self.Helpers.logger.info("** Device Sensors Callback Required (deviceSensorCallback)") + else: + self.deviceSensorCallback(msg.topic,msg.payload) + elif splitTopic[4]=='Notifications': + if self.deviceNotificationsCallback == None: + self.Helpers.logger.info("** Device Notifications Callback Required (deviceNotificationsCallback)") + else: + self.deviceNotificationsCallback(msg.topic,msg.payload) + elif splitTopic[4]=='Cameras': + if self.deviceCameraCallback == None: + self.Helpers.logger.info("** Device Camera Callback Required (cameraCallback)") + else: + self.deviceCameraCallback(msg.topic,msg.payload) + + def appChannelPub(self, channel, application, data): + + applicationChannel = '%s/Applications/%s/%s' % (self.confs['lid'], application, channel) + self.mqttClient.publish(applicationChannel,json.dumps(data)) + self.Helpers.logger.info("Published to Application "+channel+" Channel") + + def appChannelSub(self, application, channelID, qos=0): + + if application == "#": + applicationChannel = '%s/Applications/#' % (self.confs['lid']) + self.mqttClient.subscribe(applicationChannel, qos=qos) + self.Helpers.logger.info("-- Subscribed to all Application Channels") + return True + else: + applicationChannel = '%s/Applications/%s/%s' % (self.confs['lid'], application, channelID) + self.mqttClient.subscribe(applicationChannel, qos=qos) + self.Helpers.logger.info("-- Subscribed to Application " + channelID + " Channel") + return True + + def appDeviceChannelPub(self, channel, zone, device, data): + + deviceChannel = '%s/Devices/%s/%s/%s' % (self.confs['lid'], zone, device, channel) + self.mqttClient.publish(deviceChannel, json.dumps(data)) + self.Helpers.logger.info("-- Published to Device "+channel+" Channel") + + def appDeviceChannelSub(self, zone, device, channel, qos=0): + + if zone == None: + print("** Zone ID (zoneID) is required!") + return False + elif device == None: + print("** Device ID (device) is required!") + return False + elif channel == None: + print("** Channel ID (channel) is required!") + return False + else: + if device == "#": + deviceChannel = '%s/Devices/#' % (self.confs['lid']) + self.mqttClient.subscribe(deviceChannel, qos=qos) + self.Helpers.logger.info("-- Subscribed to all devices") + else: + deviceChannel = '%s/Devices/%s/%s/%s' % (self.confs['lid'], zone, device, channel) + self.mqttClient.subscribe(deviceChannel, qos=qos) + self.Helpers.logger.info("-- Subscribed to Device "+channel+" Channel") + + return True + + def on_publish(self, client, obj, mid): + self.Helpers.logger.info("Published: "+str(mid)) + + def on_log(self, client, obj, level, string): + + print(string) + + def appDisconnect(self): + self.appStatusPub("OFFLINE") + self.mqttClient.disconnect() + self.mqttClient.loop_stop() + +class Device(): + """ iotJumpWay Class + + The iotJumpWay Class provides the EMAR device with it's IoT functionality. + """ + + def __init__(self, configs): + """ Initializes the class. """ + + self.Helpers = Helpers("iotJumpWay") + self.confs = configs + + self.Helpers.logger.info("Initiating Local iotJumpWay Device.") + + if self.confs['host'] == None: + raise ConfigurationException("** Host (host) property is required") + elif self.confs['port'] == None: + raise ConfigurationException("** Port (port) property is required") + elif self.confs['lid'] == None: + raise ConfigurationException("** Location ID (lid) property is required") + elif self.confs['zid'] == None: + raise ConfigurationException("** Zone ID (zid) property is required") + elif self.confs['did'] == None: + raise ConfigurationException("** Device ID (did) property is required") + elif self.confs['dn'] == None: + raise ConfigurationException("** Device Name (dn) property is required") + elif self.confs['un'] == None: + raise ConfigurationException("** MQTT UserName (un) property is required") + elif self.confs['pw'] == None: + raise ConfigurationException("** MQTT Password (pw) property is required") + + self.mqttClient = None + self.mqttTLS = "/etc/ssl/certs/DST_Root_CA_X3.pem" + self.mqttHost = self.confs['host'] + self.mqttPort = self.confs['port'] + + self.commandsCallback = None + + self.Helpers.logger.info("Local iotJumpWay Device Initiated.") + + def connect(self): + + self.Helpers.logger.info("Initiating Local iotJumpWay Device Connection.") + + self.mqttClient = mqtt.Client(client_id=self.confs['dn'], clean_session=True) + deviceStatusTopic = '%s/Device/%s/%s/Status' % ( + self.confs['lid'], self.confs['zid'], self.confs['did']) + self.mqttClient.will_set(deviceStatusTopic, "OFFLINE", 0, False) + self.mqttClient.tls_set(self.mqttTLS, certfile=None, keyfile=None) + self.mqttClient.on_connect = self.on_connect + self.mqttClient.on_message = self.on_message + self.mqttClient.on_publish = self.on_publish + self.mqttClient.on_subscribe = self.on_subscribe + self.mqttClient.username_pw_set( + str(self.confs['un']), str(self.confs['pw'])) + self.mqttClient.connect(self.mqttHost, self.mqttPort, 10) + self.mqttClient.loop_start() + + self.Helpers.logger.info("Local iotJumpWay Device Connection Initiated.") + + def on_connect(self, client, obj, flags, rc): + + self.Helpers.logger.info("Local iotJumpWay Device Connection Successful.") + self.Helpers.logger.info("rc: " + str(rc)) + + self.statusPub("ONLINE") + + def on_subscribe(self, client, obj, mid, granted_qos): + + self.Helpers.logger.info("Local iotJumpWay Subscription: "+str(mid)) + + def on_message(self, client, obj, msg): + + self.Helpers.logger.info("Local iotJumpWay Message Received") + splitTopic=msg.topic.split("/") + + if splitTopic[1]=='Devices': + if splitTopic[4]=='Commands': + if self.commandsCallback == None: + print("** Device Commands Callback Required (commandsCallback)") + else: + self.commandsCallback(msg.topic, msg.payload) + + def statusPub(self, data): + + deviceStatusTopic = '%s/Devices/%s/%s/Status' % (self.confs['lid'], self.confs['zid'], self.confs['did']) + self.mqttClient.publish(deviceStatusTopic, data) + self.Helpers.logger.info("Published to Device Status " + deviceStatusTopic) + + def channelPub(self, channel, data): + + deviceChannel = '%s/Devices/%s/%s/%s' % (self.confs['lid'], self.confs['zid'], self.confs['did'], channel) + self.mqttClient.publish(deviceChannel, json.dumps(data)) + + def channelSub(self, channel, qos=0): + + if channel == None: + self.Helpers.logger.info("** Channel (channel) is required!") + return False + else: + deviceChannel = '%s/Devices/%s/%s/%s' % (self.confs['lid'], self.confs['zid'], self.confs['did'], channel) + self.mqttClient.subscribe(deviceChannel, qos=qos) + self.Helpers.logger.info("-- Subscribed to Device "+channel+" Channel") + + def on_publish(self, client, obj, mid): + self.Helpers.logger.info("-- Published to Device channel") + + def on_log(self, client, obj, level, string): + + print(string) + + def disconnect(self): + self.statusPub("OFFLINE") + self.mqttClient.disconnect() + self.mqttClient.loop_stop() diff --git a/Classes/MongoDB.py b/Classes/MongoDB.py index 9086f6e..f6b9f0c 100644 --- a/Classes/MongoDB.py +++ b/Classes/MongoDB.py @@ -1,7 +1,8 @@ ###################################################################################################### # -# Organization: Peter Moss Leukemia AI Research +# Organization: Asociacion De Investigacion En Inteligencia Artificial Para La Leucemia Peter Moss # Repository: HIAS: Hospital Intelligent Automation System +# Module: MongoDB # # Author: Adam Milton-Barker (AdamMiltonBarker.com) # @@ -34,21 +35,115 @@ def __init__(self): def startMongoDB(self): """ Connects to MongoDB database. """ - connection = MongoClient(self.Helpers.confs["iotJumpWay"]["ip"]) - self.mongoConn = connection[self.Helpers.confs["iotJumpWay"]["mdb"]] - self.mongoConn.authenticate(self.Helpers.confs["iotJumpWay"]["mdbu"], - self.Helpers.confs["iotJumpWay"]["mdbp"]) + connection = MongoClient(self.Helpers.confs["iotJumpWay"]["databases"]["mongo"]["ip"]) + self.mongoConn = connection[self.Helpers.confs["iotJumpWay"]["databases"]["mongo"]["db"]] + self.mongoConn.authenticate(self.Helpers.confs["iotJumpWay"]["databases"]["mongo"]["dbu"], + self.Helpers.confs["iotJumpWay"]["databases"]["mongo"]["dbp"]) + self.agentsCollection = self.mongoConn.Agents + self.locationsCollection = self.mongoConn.Locations + self.zonesCollection = self.mongoConn.Zones + self.devicesCollection = self.mongoConn.Devices + self.applicationsCollection = self.mongoConn.Applications + self.usersCollection = self.mongoConn.Users + self.patientsCollection = self.mongoConn.Patients + self.staffCollection = self.mongoConn.Staff + self.thingsCollection = self.mongoConn.Things + self.modelsCollection = self.mongoConn.Models self.Helpers.logger.info("Mongo connection started") - def insertData(self, collection, doc): + def getData(self, collection, limit, category=None, values=None): + """ Connects to MongoDB database. """ + + query = {} + + if category is not None: + query = {"category.value.0": category} + + if values is not None: + valuesArr = values.split(",") + for value in valuesArr: + pair = value.split("|") + query.update({pair[0]: pair[1]}) + + try: + entities = list(collection.find(query, {'_id': False}).limit(limit)) + self.Helpers.logger.info("Mongo data found OK") + return entities + except: + e = sys.exc_info() + self.Helpers.logger.info("Mongo data find FAILED!") + self.Helpers.logger.info(str(e)) + return False + + def getDataById(self, collection, _id, attrs): + """ Connects to MongoDB database. """ + + fields = {'_id': False} + if attrs is not None: + attribs = attrs.split(",") + for attr in attribs: + fields.update({attr: True}) + + try: + entity = list(collection.find({'id': _id}, fields)) + self.Helpers.logger.info("Mongo data found OK") + return entity + except: + e = sys.exc_info() + self.Helpers.logger.info("Mongo data find FAILED!") + self.Helpers.logger.info(str(e)) + return False + + def insertData(self, collection, doc, typeof): """ Connects to MongoDB database. """ try: _id = collection.insert(doc) self.Helpers.logger.info("Mongo data inserted OK") + if typeof is "Device": + self.locationsCollection.find_one_and_update( + {"id": doc.lid.entity}, + {'$inc': {'devices.value': 1}} + ) + self.zonesCollection.find_one_and_update( + {"id": doc.zid.entity}, + {'$inc': {'devices.value': 1}} + ) + if typeof is "Application": + self.locationsCollection.find_one_and_update( + {"id": doc.lid.entity}, + {'$inc': {'applications.value': 1}} + ) + self.Helpers.logger.info("Mongo data update OK") return _id except: e = sys.exc_info() self.Helpers.logger.info("Mongo data inserted FAILED!") self.Helpers.logger.info(str(e)) return False + + def updateData(self, _id, collection, doc): + """ Connects to MongoDB database. """ + + try: + collection.update_one({"id" : _id}, {"$set": doc}); + self.Helpers.logger.info("Mongo data update OK") + return True + except: + e = sys.exc_info() + self.Helpers.logger.info("Mongo data update FAILED!") + self.Helpers.logger.info(str(e)) + return False + + def deleteData(self, _id, collection): + """ Connects to MongoDB database. """ + + try: + collection.delete_one({"id": _id}); + self.Helpers.logger.info("Mongo data update OK") + return True + except: + e = sys.exc_info() + self.Helpers.logger.info("Mongo data update FAILED!") + self.Helpers.logger.info(str(e)) + return False diff --git a/Classes/MySQL.py b/Classes/MySQL.py deleted file mode 100644 index fd0f794..0000000 --- a/Classes/MySQL.py +++ /dev/null @@ -1,333 +0,0 @@ -###################################################################################################### -# -# Organization: Peter Moss Leukemia AI Research -# Repository: HIAS: Hospital Intelligent Automation System -# -# Author: Adam Milton-Barker (AdamMiltonBarker.com) -# -# Title: MySQL Class -# Description: MySQL functions for the Hospital Intelligent Automation System. -# License: MIT License -# Last Modified: 2020-09-20 -# -###################################################################################################### - -import bcrypt -import MySQLdb -import sys -import time - -from datetime import datetime -from datetime import timedelta - -from Classes.Helpers import Helpers - - -class MySQL(): - """ MySQL Class - - MySQL functions for the Hospital Intelligent Automation System. - """ - - def __init__(self): - """ Initializes the class. """ - - self.Helpers = Helpers("MySQL") - self.Helpers.logger.info("MySQL Class initialization complete.") - - def startMySQL(self): - """ Connects to MySQL database. """ - - self.mysqlConn = MySQLdb.connect(host=self.Helpers.confs["iotJumpWay"]["ip"], - user=self.Helpers.confs["iotJumpWay"]["dbuser"], - passwd=self.Helpers.confs["iotJumpWay"]["dbpass"], - db=self.Helpers.confs["iotJumpWay"]["dbname"]) - self.Helpers.logger.info("MySQL connection started") - - def getApplication(self, app): - """ Get application details """ - - try: - cur = self.mysqlConn.cursor() - cur.execute(""" - SELECT * - FROM mqtta - WHERE id=%s - """, (int(app),)) - appDetails = cur.fetchone() - cur.close() - self.Helpers.logger.info("App details select OK!") - return appDetails - except: - e = sys.exc_info() - self.Helpers.logger.info("App details select failed!") - self.Helpers.logger.info(str(e)) - return "" - - def updateApplicationStatus(self, payload, splitTopic): - """ Updates the status of an application """ - - try: - cur = self.mysqlConn.cursor() - cur.execute(""" - UPDATE mqtta - SET status=%s - WHERE id=%s - """, (str(payload), splitTopic[2])) - self.mysqlConn.commit() - cur.close() - self.Helpers.logger.info("Mysql Application status updated OK") - except: - e = sys.exc_info() - self.mysqlConn.rollback() - self.Helpers.logger.info("Mysql Application status update FAILED!") - self.Helpers.logger.info(str(e)) - - def updateApplication(self, typeof, data, splitTopic): - """ Updates an application """ - - try: - cur = self.mysqlConn.cursor() - cur.execute(""" - UPDATE mqtta - SET cpu=%s, - mem=%s, - hdd=%s, - tempr=%s, - lt=%s, - lg=%s - WHERE id=%s - """, (data["CPU"], data["Memory"], data["Diskspace"], data["Temperature"], data["Latitude"], data["Longitude"], splitTopic[2])) - self.mysqlConn.commit() - cur.close() - self.Helpers.logger.info("Mysql " + typeof + " application updated OK") - except: - e = sys.exc_info() - self.mysqlConn.rollback() - self.Helpers.logger.info("Mysql " + typeof + " application updated FAILED ") - self.Helpers.logger.info(str(e)) - - def getDevice(self, device): - """ Get application details """ - - try: - cur = self.mysqlConn.cursor() - cur.execute(""" - SELECT * - FROM mqttld - WHERE id=%s - """, (int(device),)) - dvcDetails = cur.fetchone() - cur.close() - self.Helpers.logger.info("Device details select OK!") - return dvcDetails - except: - e = sys.exc_info() - self.Helpers.logger.info("Device details select failed!") - self.Helpers.logger.info(str(e)) - return "" - - def updateDeviceStatus(self, payload, device): - """ Updates the status of a device """ - - try: - cur = self.mysqlConn.cursor() - cur.execute(""" - UPDATE mqttld - SET status=%s - WHERE id=%s - """, (str(payload), device)) - self.mysqlConn.commit() - cur.close() - self.Helpers.logger.info("Mysql Device status updated OK") - except: - e = sys.exc_info() - self.mysqlConn.rollback() - self.Helpers.logger.info("Mysql Device status update FAILED") - self.Helpers.logger.info(str(e)) - - def updateDevice(self, typeof, data, device): - """ Updates a device """ - - try: - cur = self.mysqlConn.cursor() - cur.execute(""" - UPDATE mqttld - SET cpu=%s, - mem=%s, - hdd=%s, - tempr=%s, - lt=%s, - lg=%s - WHERE id=%s - """, (data["CPU"], data["Memory"], data["Diskspace"], data["Temperature"], data["Latitude"], data["Longitude"], device)) - self.mysqlConn.commit() - cur.close() - self.Helpers.logger.info("Mysql " + typeof + " device updated OK") - except: - e = sys.exc_info() - self.mysqlConn.rollback() - self.Helpers.logger.info("Mysql " + typeof + " device update FAILED!") - self.Helpers.logger.info(str(e)) - - def getNLU(self, splitTopic): - """ Get NLU device """ - - try: - cur = self.mysqlConn.cursor() - cur.execute(""" - SELECT genisysainlu.did - FROM genisysainlu - INNER JOIN mqttld - ON genisysainlu.did = mqttld.id - WHERE mqttld.zid = %s - && mqttld.status=%s - """, (splitTopic[2], "ONLINE")) - nlu = cur.fetchone() - cur.close() - self.Helpers.logger.info("Camera NLU details: " + str(nlu)) - return nlu - except: - e = sys.exc_info() - self.Helpers.logger.info("Camera NLU details select failed!") - self.Helpers.logger.info(str(e)) - return "" - pass - - def updateUserLocation(self, splitTopic, data): - """ Get NLU device """ - - try: - cur = self.mysqlConn.cursor() - cur.execute(""" - UPDATE users - SET cz=%s, - czt=%s - WHERE id=%s - """, (splitTopic[2], time.time(), int(data["Value"]))) - self.mysqlConn.commit() - cur.close() - self.Helpers.logger.info("Mysql user location data updated OK") - except: - e = sys.exc_info() - self.mysqlConn.rollback() - self.Helpers.logger.info("Mysql user location update FAILED") - self.Helpers.logger.info(str(e)) - - def getUser(self, data): - """ Get user details """ - - cTime = datetime.now() - hb = cTime - timedelta(hours=1) - hbe = int(hb.timestamp()) - - try: - cur = self.mysqlConn.cursor() - cur.execute(""" - SELECT users.name, - users.aid, - mqtta.status - FROM users - INNER JOIN mqtta - ON users.aid = mqtta.id - WHERE users.id=%s - && (users.welcomed = 0 || users.welcomed <= %s) - """, (int(data["Value"]), hbe)) - userDetails = cur.fetchone() - cur.close() - self.Helpers.logger.info("User details: " + str(userDetails)) - return userDetails - except: - e = sys.exc_info() - self.Helpers.logger.info("User details select failed ") - self.Helpers.logger.info(str(e)) - return "" - - def getUserNFC(self, uid): - """ Checks user NFC UID """ - - try: - cur = self.mysqlConn.cursor() - cur.execute(""" - SELECT nfc - FROM users - WHERE users.nfc=%s - """, (uid,)) - uuid = cur.fetchone() - cur.close() - if uuid[0] is not None: - self.Helpers.logger.info("NFC UID OK!") - return True - else: - self.Helpers.logger.info("NFC UID Not Authorized!") - return False - except: - e = sys.exc_info() - self.Helpers.logger.info("NFC UID select failed ") - self.Helpers.logger.info(str(e)) - return "" - - def updateUser(self, data): - """ Get user details """ - - try: - cur = self.mysqlConn.cursor() - cur.execute(""" - UPDATE users - SET welcomed=%s - WHERE id=%s - """, (time.time(), int(data["Value"]))) - self.mysqlConn.commit() - cur.close() - self.Helpers.logger.info("Mysql user welcome updated OK") - except: - e = sys.exc_info() - self.mysqlConn.rollback() - self.Helpers.logger.info("Mysql user welcome updated FAILED!") - self.Helpers.logger.info(str(e)) - - def insertDataTransaction(self, aid, did, action, thash): - """ Get user details """ - - try: - cur = self.mysqlConn.cursor() - cur.execute(""" - INSERT INTO transactions (aid, did, action, hash, time) - VALUES (%s, %s, %s, %s, %s) - """, (aid, did, action, thash, time.time())) - self.mysqlConn.commit() - hashid = cur.lastrowid - self.Helpers.logger.info("Transaction stored in database!") - - cur = self.mysqlConn.cursor() - cur.execute(""" - INSERT INTO history (taid, tdid, action, hash, time) - VALUES (%s, %s, %s, %s, %s) - """, (aid, did, action, hashid, time.time())) - self.mysqlConn.commit() - cur.close() - self.Helpers.logger.info("History stored in database!") - except: - e = sys.exc_info() - self.mysqlConn.rollback() - self.Helpers.logger.info("Transaction history FAILED!") - self.Helpers.logger.info(str(e)) - - def getContracts(self): - """ Get all smart contracts """ - - try: - cur = self.mysqlConn.cursor() - cur.execute(""" - SELECT * - FROM contracts - """) - contracts = cur.fetchall() - cur.close() - self.Helpers.logger.info("Got contracts: " + str(len(contracts))) - return contracts - except: - e = sys.exc_info() - self.Helpers.logger.info("Contracts select failed ") - self.Helpers.logger.info(str(e)) - return "" diff --git a/Classes/TassAI.py b/Classes/TassAI.py index 8fde093..0ca4d80 100644 --- a/Classes/TassAI.py +++ b/Classes/TassAI.py @@ -1,7 +1,8 @@ ###################################################################################################### # -# Organization: Peter Moss Leukemia AI Research -# Repository: HIAS: Hospital Intelligent Automation System +# Organization: Asociacion De Investigacion En Inteligencia Artificial Para La Leucemia Peter Moss +# Repository: HIAS: Hospital Intelligent Automation System +# Project: TassAI # # Author: Adam Milton-Barker (AdamMiltonBarker.com) # @@ -37,7 +38,7 @@ def __init__(self): self.Helpers = Helpers("TassAI", False) self.qs = 16 - self.context = InferenceContext([self.Helpers.confs["TassAI"]["runas"], self.Helpers.confs["TassAI"]["runas"], self.Helpers.confs["TassAI"]["runas"]], "", "", "") + self.context = InferenceContext([self.Helpers.confs["iotJumpWay"]["MQTT"]["TassAI"]["runas"], self.Helpers.confs["iotJumpWay"]["MQTT"]["TassAI"]["runas"], self.Helpers.confs["iotJumpWay"]["MQTT"]["TassAI"]["runas"]], "", "", "") self.Helpers.logger.info("TassAI Helper Class initialization complete.") @@ -45,30 +46,30 @@ def load_models(self): """ Loads all models. """ face_detector_net = self.load_model( - self.Helpers.confs["TassAI"]["detection"]) + self.Helpers.confs["iotJumpWay"]["MQTT"]["TassAI"]["detection"]) face_detector_net.reshape({"data": [1, 3, 384, 672]}) landmarks_net = self.load_model( - self.Helpers.confs["TassAI"]["landmarks"]) + self.Helpers.confs["iotJumpWay"]["MQTT"]["TassAI"]["landmarks"]) face_reid_net = self.load_model( - self.Helpers.confs["TassAI"]["reidentification"]) + self.Helpers.confs["iotJumpWay"]["MQTT"]["TassAI"]["reidentification"]) self.face_detector = FaceDetector(face_detector_net, - confidence_threshold=0.6, - roi_scale_factor=1.15) + confidence_threshold=0.6, + roi_scale_factor=1.15) self.landmarks_detector = LandmarksDetector(landmarks_net) self.face_identifier = FaceIdentifier(face_reid_net, - match_threshold=0.3, - match_algo='HUNGARIAN') + match_threshold=0.3, + match_algo='HUNGARIAN') - self.face_detector.deploy(self.Helpers.confs["TassAI"]["runas"], self.context) - self.landmarks_detector.deploy(self.Helpers.confs["TassAI"]["runas"], self.context, + self.face_detector.deploy(self.Helpers.confs["iotJumpWay"]["MQTT"]["TassAI"]["runas"], self.context) + self.landmarks_detector.deploy(self.Helpers.confs["iotJumpWay"]["MQTT"]["TassAI"]["runas"], self.context, queue_size=self.qs) - self.face_identifier.deploy(self.Helpers.confs["TassAI"]["runas"], self.context, - queue_size=self.qs) + self.face_identifier.deploy(self.Helpers.confs["iotJumpWay"]["MQTT"]["TassAI"]["runas"], self.context, + queue_size=self.qs) self.Helpers.logger.info("Models loaded") @@ -87,7 +88,7 @@ def load_model(self, model_path): def load_known(self): """ Loads known data. """ - self.faces_database = FacesDatabase(self.Helpers.confs["TassAI"]["data"], self.face_identifier, + self.faces_database = FacesDatabase(self.Helpers.confs["iotJumpWay"]["MQTT"]["TassAI"]["data"], self.face_identifier, self.landmarks_detector, self.face_detector, True) self.face_identifier.set_faces_database(self.faces_database) self.Helpers.logger.info("Database is built, registered %s identities" % @@ -154,8 +155,8 @@ def draw_detection_roi(self, frame, roi, identity): if identity.id != FaceIdentifier.UNKNOWN_ID: text += ' %.2f%%' % (100.0 * (1 - identity.distance)) self.draw_text_with_background(frame, text, - roi.position - line_height * 0.5, - font, scale=text_scale) + roi.position - line_height * 0.5, + font, scale=text_scale) return frame, label diff --git a/Classes/iotJumpWay.py b/Classes/iotJumpWay.py deleted file mode 100644 index 7d5e5d1..0000000 --- a/Classes/iotJumpWay.py +++ /dev/null @@ -1,331 +0,0 @@ -###################################################################################################### -# -# Organization: Peter Moss Leukemia AI Research -# Repository: HIAS: Hospital Intelligent Automation System -# -# Author: Adam Milton-Barker (AdamMiltonBarker.com) -# Contributors: -# Title: iotJumpWay Class -# Description: The iotJumpWay Class provides the Hospital Intelligent Automation System with it's -# IoT functionality. -# License: MIT License -# Last Modified: 2020-06-04 -# -###################################################################################################### - -import inspect, json, os - -import paho.mqtt.client as mqtt - -from Classes.Helpers import Helpers - -class Application(): - """ iotJumpWay Class - - The iotJumpWay Class provides the Hospital Intelligent Automation System with - it's IoT functionality. - """ - - def __init__(self, configs): - """ Initializes the class. """ - - self.Helpers = Helpers("iotJumpWay") - self.confs = configs - - self.Helpers.logger.info("Initiating Local iotJumpWay Application.") - - if self.confs['host'] == None: - raise ConfigurationException("** Host (host) property is required") - elif self.confs['port'] == None: - raise ConfigurationException("** Port (port) property is required") - elif self.confs['lid'] == None: - raise ConfigurationException("** Location ID (lid) property is required") - elif self.confs['aid'] == None: - raise ConfigurationException("** Application ID (aid) property is required") - elif self.confs['an'] == None: - raise ConfigurationException("** Application Name (an) property is required") - elif self.confs['un'] == None: - raise ConfigurationException("** MQTT UserName (un) property is required") - elif self.confs['pw'] == None: - raise ConfigurationException("** MQTT Password (pw) property is required") - - self.mqttClient = None - self.mqttTLS = "/etc/ssl/certs/DST_Root_CA_X3.pem" - self.mqttHost = self.confs['host'] - self.mqttPort = self.confs['port'] - - self.appCommandsCallback = None - self.appSensorCallback = None - self.appStatusCallback = None - self.appTriggerCallback = None - self.appLifeCallback = None - self.deviceCameraCallback = None - self.deviceCommandsCallback = None - self.deviceLifeCallback = None - self.deviceNfcCallback = None - self.deviceSensorCallback = None - self.deviceStatusCallback = None - self.deviceTriggerCallback = None - - self.Helpers.logger.info("JumpWayMQTT Application Initiated.") - - def connect(self): - - self.Helpers.logger.info("Initiating Local iotJumpWay Application Connection.") - - self.mqttClient = mqtt.Client(client_id = self.confs['an'], clean_session = True) - applicationStatusTopic = '%s/Applications/%s/Status' % (self.confs['lid'], self.confs['aid']) - self.mqttClient.will_set(applicationStatusTopic, "OFFLINE", 0, False) - self.mqttClient.tls_set(self.mqttTLS, certfile=None, keyfile=None) - self.mqttClient.on_connect = self.on_connect - self.mqttClient.on_message = self.on_message - self.mqttClient.on_publish = self.on_publish - self.mqttClient.on_subscribe = self.on_subscribe - self.mqttClient.username_pw_set(str(self.confs['un']), str(self.confs['pw'])) - self.mqttClient.connect(self.mqttHost, self.mqttPort, 10) - self.mqttClient.loop_start() - - self.Helpers.logger.info("Local iotJumpWay Application Connection Initiated.") - - def on_connect(self, client, obj, flags, rc): - - self.Helpers.logger.info("Local iotJumpWay Application Connection Successful.") - self.Helpers.logger.info("rc: " + str(rc)) - - self.appStatusPub("ONLINE") - - def appStatusPub(self, data): - - deviceStatusTopic = '%s/Applications/%s/Status' % (self.confs['lid'], self.confs['aid']) - self.mqttClient.publish(deviceStatusTopic, data) - self.Helpers.logger.info("Published to Application Status " + deviceStatusTopic) - - def on_subscribe(self, client, obj, mid, granted_qos): - - self.Helpers.logger.info("JumpWayMQTT Subscription: "+str(self.confs['an'])) - - def on_message(self, client, obj, msg): - - self.Helpers.logger.info("JumpWayMQTT Message Received") - splitTopic=msg.topic.split("/") - - if splitTopic[1]=='Applications': - if splitTopic[3]=='Status': - if self.appStatusCallback == None: - self.Helpers.logger.info("** Application Status Callback Required (appStatusCallback)") - else: - self.appStatusCallback(msg.topic,msg.payload) - elif splitTopic[3]=='Command': - if self.cameraCallback == None: - self.Helpers.logger.info("** Application Camera Callback Required (cameraCallback)") - else: - self.cameraCallback(msg.topic,msg.payload) - elif splitTopic[3]=='Life': - if self.appLifeCallback == None: - self.Helpers.logger.info("** Application Life Callback Required (appLifeCallback)") - else: - self.appLifeCallback(msg.topic,msg.payload) - elif splitTopic[1] == 'Devices': - if splitTopic[4] == 'Actuators': - if self.deviceActuatorCallback == None: - self.Helpers.logger.info("** Device Actuator Callback Required (deviceActuatorCallback)") - else: - self.deviceActuatorCallback(msg.topic, msg.payload) - elif splitTopic[4]=='Commands': - if self.deviceCommandsCallback == None: - self.Helpers.logger.info("** Device Commands Callback Required (deviceCommandsCallback)") - else: - self.deviceCommandsCallback(msg.topic,msg.payload) - elif splitTopic[4]=='Life': - if self.deviceLifeCallback == None: - self.Helpers.logger.info("** Device Life Callback Required (deviceLifeCallback)") - else: - self.deviceLifeCallback(msg.topic, msg.payload) - elif splitTopic[4]=='NFC': - if self.deviceNfcCallback == None: - self.Helpers.logger.info("** Device NFC Callback Required (deviceNfcCallback)") - else: - self.deviceNfcCallback(msg.topic, msg.payload) - elif splitTopic[4] == 'Status': - if self.deviceStatusCallback == None: - self.Helpers.logger.info("** Device Status Callback Required (deviceStatusCallback)") - else: - self.deviceStatusCallback(msg.topic, msg.payload) - elif splitTopic[4]=='Sensors': - if self.deviceSensorCallback == None: - self.Helpers.logger.info("** Device Sensors Callback Required (deviceSensorCallback)") - else: - self.deviceSensorCallback(msg.topic,msg.payload) - elif splitTopic[4]=='Notifications': - if self.deviceNotificationsCallback == None: - self.Helpers.logger.info("** Device Notifications Callback Required (deviceNotificationsCallback)") - else: - self.deviceNotificationsCallback(msg.topic,msg.payload) - elif splitTopic[4]=='Cameras': - if self.deviceCameraCallback == None: - self.Helpers.logger.info("** Device Camera Callback Required (cameraCallback)") - else: - self.deviceCameraCallback(msg.topic,msg.payload) - - def appChannelPub(self, channel, application, data): - - applicationChannel = '%s/Applications/%s/%s' % (self.confs['lid'], application, channel) - self.mqttClient.publish(applicationChannel,json.dumps(data)) - print("Published to Application "+channel+" Channel") - - def appChannelSub(self, application, channelID, qos=0): - - if application == "#": - applicationChannel = '%s/Applications/#' % (self.confs['lid']) - self.mqttClient.subscribe(applicationChannel, qos=qos) - self.Helpers.logger.info("-- Subscribed to all Application Channels") - return True - else: - applicationChannel = '%s/Applications/%s/%s' % (self.confs['lid'], application, channelID) - self.mqttClient.subscribe(applicationChannel, qos=qos) - self.Helpers.logger.info("-- Subscribed to Application " + channelID + " Channel") - return True - - def appDeviceChannelPub(self, channel, zone, device, data): - - deviceChannel = '%s/Devices/%s/%s/%s' % (self.confs['lid'], zone, device, channel) - self.mqttClient.publish(deviceChannel, json.dumps(data)) - self.Helpers.logger.info("-- Published to Device "+channel+" Channel") - - def appDeviceChannelSub(self, zone, device, channel, qos=0): - - if zone == None: - print("** Zone ID (zoneID) is required!") - return False - elif device == None: - print("** Device ID (device) is required!") - return False - elif channel == None: - print("** Channel ID (channel) is required!") - return False - else: - if device == "#": - deviceChannel = '%s/Devices/#' % (self.confs['lid']) - self.mqttClient.subscribe(deviceChannel, qos=qos) - self.Helpers.logger.info("-- Subscribed to all devices") - else: - deviceChannel = '%s/Devices/%s/%s/%s' % (self.confs['lid'], zone, device, channel) - self.mqttClient.subscribe(deviceChannel, qos=qos) - self.Helpers.logger.info("-- Subscribed to Device "+channel+" Channel") - - return True - - def on_publish(self, client, obj, mid): - - print("-- Published: "+str(mid)) - - def on_log(self, client, obj, level, string): - - print(string) - - def appDisconnect(self): - self.appStatusPub("OFFLINE") - self.mqttClient.disconnect() - self.mqttClient.loop_stop() - -class Device(): - """ iotJumpWay Class - - The iotJumpWay Class provides the EMAR device with it's IoT functionality. - """ - - def __init__(self): - """ Initializes the class. """ - - self.Helpers = Helpers("iotJumpWay") - - self.Helpers.logger.info("Initiating Local iotJumpWay Device.") - - self.mqttClient = None - self.mqttTLS = "/etc/ssl/certs/DST_Root_CA_X3.pem" - self.mqttHost = self.Helpers.confs["iotJumpWay"]['host'] - self.mqttPort = self.Helpers.confs["iotJumpWay"]['port'] - - self.commandsCallback = None - - self.Helpers.logger.info("JumpWayMQTT Device Initiated.") - - def connect(self): - - self.Helpers.logger.info("Initiating Local iotJumpWay Device Connection.") - - self.mqttClient = mqtt.Client(client_id = self.Helpers.confs["iotJumpWay"]['dn'], clean_session = True) - deviceStatusTopic = '%s/Device/%s/%s/Status' % (self.Helpers.confs["iotJumpWay"]['lid'], self.Helpers.confs["iotJumpWay"]['zid'], self.Helpers.confs["iotJumpWay"]['did']) - self.mqttClient.will_set(deviceStatusTopic, "OFFLINE", 0, False) - self.mqttClient.tls_set(self.mqttTLS, certfile=None, keyfile=None) - self.mqttClient.on_connect = self.on_connect - self.mqttClient.on_message = self.on_message - self.mqttClient.on_publish = self.on_publish - self.mqttClient.on_subscribe = self.on_subscribe - self.mqttClient.username_pw_set(str(self.Helpers.confs["iotJumpWay"]['gun']), str(self.Helpers.confs["iotJumpWay"]['gpw'])) - self.mqttClient.connect(self.mqttHost, self.mqttPort, 10) - self.mqttClient.loop_start() - - self.Helpers.logger.info("Local iotJumpWay Device Connection Initiated.") - - def on_connect(self, client, obj, flags, rc): - - self.Helpers.logger.info("Local iotJumpWay Device Connection Successful.") - self.Helpers.logger.info("rc: " + str(rc)) - - self.statusPub("ONLINE") - - def on_subscribe(self, client, obj, mid, granted_qos): - - self.Helpers.logger.info("JumpWayMQTT Subscription: "+str(mid)) - - def on_message(self, client, obj, msg): - - print("JumpWayMQTT Message Received") - splitTopic=msg.topic.split("/") - - if splitTopic[1]=='Devices': - if splitTopic[4]=='Commands': - if self.commandsCallback == None: - print("** Device Commands Callback Required (commandsCallback)") - else: - self.commandsCallback(msg.topic, msg.payload) - elif splitTopic[4]=='Triggers': - if self.triggersCallback == None: - print("** Device Notifications Callback Required (deviceNotificationsCallback)") - else: - self.triggersCallback(msg.topic, msg.payload) - - def statusPub(self, data): - - deviceStatusTopic = '%s/Devices/%s/%s/Status' % (self.Helpers.confs["iotJumpWay"]['lid'], self.Helpers.confs["iotJumpWay"]['zid'], self.Helpers.confs["iotJumpWay"]['did']) - self.mqttClient.publish(deviceStatusTopic, data) - self.Helpers.logger.info("Published to Device Status " + deviceStatusTopic) - - def channelPub(self, channel, data): - - deviceChannel = '%s/Devices/%s/%s/%s' % (self.Helpers.confs["iotJumpWay"]['lid'], self.Helpers.confs["iotJumpWay"]['zid'], self.Helpers.confs["iotJumpWay"]['did'], channel) - self.mqttClient.publish(deviceChannel, json.dumps(data)) - - def channelSub(self, channel, qos=0): - - if channel == None: - self.Helpers.logger.info("** Channel (channel) is required!") - return False - else: - deviceChannel = '%s/Devices/%s/%s/%s' % (self.Helpers.confs["iotJumpWay"]['lid'], self.Helpers.confs["iotJumpWay"]['zid'], self.Helpers.confs["iotJumpWay"]['did'], channel) - self.mqttClient.subscribe(deviceChannel, qos=qos) - self.Helpers.logger.info("-- Subscribed to Device "+channel+" Channel") - - def on_publish(self, client, obj, mid): - - self.Helpers.logger.info("-- Published to Device channel") - - def on_log(self, client, obj, level, string): - - print(string) - - def disconnect(self): - self.statusPub("OFFLINE") - self.mqttClient.disconnect() - self.mqttClient.loop_stop() From ad2fc7c0fb6d329dab4bc87c0e474ecb0f647621 Mon Sep 17 00:00:00 2001 From: AdamMiltonBarker Date: Thu, 22 Oct 2020 00:53:26 +0200 Subject: [PATCH 05/22] 2.0.0 Context Broker --- Context/Agents.py | 121 +++++++++++++++ Context/Broker.py | 366 ++++++++++++++++++++++++++++++++++++++++++++ Context/Entities.py | 249 ++++++++++++++++++++++++++++++ 3 files changed, 736 insertions(+) create mode 100644 Context/Agents.py create mode 100644 Context/Broker.py create mode 100644 Context/Entities.py diff --git a/Context/Agents.py b/Context/Agents.py new file mode 100644 index 0000000..8cc825f --- /dev/null +++ b/Context/Agents.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +###################################################################################################### +# +# Organization: Peter Moss Leukemia AI Research +# Repository: HIAS: Hospital Intelligent Automation System +# +# Author: Adam Milton-Barker (AdamMiltonBarker.com) +# +# Title: HIAS HDSI Context Broker +# Description: The HIAS HDSI Context Broker handles contextual data for all iotJumpWay +# devices/applications and IoT Agents. The Context Broker uses the HDSI (HIAS +# Data Services Interface) API V1, based on Open Mobile Alliance's NGSI V2. +# License: MIT License +# Last Modified: 2020-10-18 +# +###################################################################################################### + +import json +import jsonpickle +import os +import sys + +from bson import json_util, ObjectId +from flask import Response + +from Classes.Helpers import Helpers + + +class Agents(): + """ Agents Class + + Handles IoT Agent functionality for the HIAS HDSI Context Broker. + """ + + def __init__(self, mongoConnection): + """ Initializes the class. """ + + self.Helpers = Helpers("Agents") + self.MongoDB = mongoConnection + self.Helpers.logger.info("Agents Class initialization complete.") + + def createAgent(self, data): + """ Creates a new NDSI IoT Agent """ + + _id = self.MongoDB.insertData( + self.MongoDB.applicationsCollection, data, data["type"]) + + resp = { + "Response": "OK", + "ID": str(_id), + "Agent": json.loads(json_util.dumps(data)) + } + + if str(_id) is not False: + return self.respond(201, resp, "v1/agents/" + data["id"]) + else: + return self.respond(400, { + "Response": "Failed", + "Error": "BadRequest", + "Description": "Entity storage failed!" + }) + + def getAgents(self, limit=0): + """ Gets all NDSI IoT Agents """ + + agents = self.MongoDB.getData(self.MongoDB.applicationsCollection, limit, "IoT Agent") + + if not agents: + return self.respond(404, { + "Response": "Failed", + "Error": "NotFound", + "Description": "No agents exist!" + }) + else: + resp = { + "Response": "OK", + "Data": json.loads(json_util.dumps(agents)) + } + return self.respond(200, resp) + + def getAgent(self, _id, attrs): + """ Gets a specific NDSI IoT Agent """ + + agent = self.MongoDB.getDataById( + self.MongoDB.applicationsCollection, _id, attrs) + + if not agent: + return self.respond(404, { + "Response": "Failed", + "Error": "NotFound", + "Description": "Agent does not exist!" + }) + else: + resp = { + "Response": "OK", + "Data": json.loads(json_util.dumps(agent[0])) + } + return self.respond(200, resp) + + def updateAgent(self, _id, data): + """ Updates an NDSI IoT Agent """ + + updated = self.MongoDB.updateData( + _id, self.MongoDB.applicationsCollection, data) + + if updated is True: + return self.respond(200, { + "Response": "OK" + }) + else: + return self.respond(400, { + "Response": "OK", + "Error": "BadRequest", + "Description": "Agent update failed!" + }) + + def respond(self, responseCode, response, location=None): + """ Builds the request repsonse """ + + return Response(response=json.dumps(response, indent=4), status=responseCode, + mimetype="application/json") diff --git a/Context/Broker.py b/Context/Broker.py new file mode 100644 index 0000000..c53cbfc --- /dev/null +++ b/Context/Broker.py @@ -0,0 +1,366 @@ +#!/usr/bin/env python3 +###################################################################################################### +# +# Organization: Peter Moss Leukemia AI Research +# Repository: HIAS: Hospital Intelligent Automation System +# +# Author: Adam Milton-Barker (AdamMiltonBarker.com) +# +# Title: HIAS HDSI Context Broker +# Description: The HIAS HDSI Context Broker handles contextual data for all iotJumpWay +# devices/applications and IoT Agents. The Context Broker uses the HDSI (HIAS +# Data Services Interface) API V1, based on Open Mobile Alliance's NGSI V2. +# License: MIT License +# Last Modified: 2020-10-18 +# +###################################################################################################### + +import json +import jsonpickle +import psutil +import requests +import os +import signal +import sys +import threading + +sys.path.insert(0, os.path.abspath( + os.path.join(os.path.dirname(__file__), '..'))) + +from bson import json_util, ObjectId +from flask import Flask, request, Response +from threading import Thread + +from Classes.Helpers import Helpers +from Classes.MongoDB import MongoDB +from Classes.MQTT import Application + +from Agents import Agents +from Entities import Entities + +class ContextBroker(): + """ HIAS HDSI Context Broker + + The HIAS HDSI Context Broker handles contextual data + for all iotJumpWay devices/applications and IoT Agents. + """ + + def __init__(self): + """ Initializes the class. """ + + self.Helpers = Helpers("ContextBroker") + self.Helpers.logger.info( + "HIAS iotJumpWay Context Broker initialization complete.") + + def iotConnection(self): + """ Initiates the iotJumpWay connection. """ + + self.Application = Application({ + "host": self.Helpers.confs["iotJumpWay"]["host"], + "port": self.Helpers.confs["iotJumpWay"]["ContextBroker"]["iport"], + "lid": self.Helpers.confs["iotJumpWay"]["ContextBroker"]["lid"], + "aid": self.Helpers.confs["iotJumpWay"]["ContextBroker"]["aid"], + "an": self.Helpers.confs["iotJumpWay"]["ContextBroker"]["an"], + "un": self.Helpers.confs["iotJumpWay"]["ContextBroker"]["un"], + "pw": self.Helpers.confs["iotJumpWay"]["ContextBroker"]["pw"] + }) + self.Application.connect() + + def life(self): + """ Sends vital statistics to HIAS """ + + cpu = psutil.cpu_percent() + mem = psutil.virtual_memory()[2] + hdd = psutil.disk_usage('/').percent + tmp = psutil.sensors_temperatures()['coretemp'][0].current + r = requests.get('http://ipinfo.io/json?token=' + + self.Helpers.confs["iotJumpWay"]["ipinfo"]) + data = r.json() + location = data["loc"].split(',') + + # Send iotJumpWay notification + self.Application.appChannelPub("Life", self.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["aid"], { + "CPU": str(cpu), + "Memory": str(mem), + "Diskspace": str(hdd), + "Temperature": str(tmp), + "Latitude": float(location[0]), + "Longitude": float(location[1]) + }) + + self.Helpers.logger.info("Broker life statistics published.") + threading.Timer(300.0, self.life).start() + + def mongoDbConnection(self): + """ Initiates the MongoDB connection class. """ + + self.MongoDB = MongoDB() + self.MongoDB.startMongoDB() + + def configureBroker(self): + """ Configures the Context Broker. """ + + self.Entities = Entities(self.MongoDB) + self.Agents = Agents(self.MongoDB) + + def getBroker(self): + + return { + "Broker": { + "Version": self.Helpers.confs["iotJumpWay"]["ContextBroker"]["version"], + "Host": self.Helpers.confs["iotJumpWay"]["host"], + "IP": self.Helpers.confs["iotJumpWay"]["ip"], + "Port": self.Helpers.confs["iotJumpWay"]["ContextBroker"]["port"], + "Endpoint": self.Helpers.confs["iotJumpWay"]["ContextBroker"]["address"], + "Locations": self.MongoDB.locationsCollection.count_documents({"type": "Location"}), + "Zones": self.MongoDB.locationsCollection.count_documents({"type": "Zone"}), + }, + "Entities": { + "Applications": { + "Count": self.MongoDB.applicationsCollection.count_documents({"type": "Application"}), + "IotAgents": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "IoT Agent"}), + "AiAgents": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "AI Agent"}), + "Staff": { + "Administration": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "Management"}), + "Director": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "Director"}), + "Developer": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "Developer"}), + "Doctor": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "Doctor"}), + "Management": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "Management"}), + "Network Security": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "Network Security"}), + "Nurse": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "Nurse"}), + "Security": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "Security"}), + "Supervisor": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "Supervisor"}), + "Cancelled": { + "Administration": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "Management", "cancelled.value": "1"}), + "Director": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "Director", "cancelled.value": "1"}), + "Developer": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "Developer", "cancelled.value": "1"}), + "Doctor": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "Doctor", "cancelled.value": "1"}), + "Management": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "Management", "cancelled.value": "1"}), + "Network Security": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "Network Security", "cancelled.value": "1"}), + "Nurse": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "Nurse", "cancelled.value": "1"}), + "Security": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "Security", "cancelled.value": "1"}), + "Supervisor": self.MongoDB.applicationsCollection.count_documents({"type": "Application", "category.value.0": "Supervisor", "cancelled.value": "1"}) + } + } + }, + "Devices": { + "Count": self.MongoDB.devicesCollection.count_documents({}), + "Server": self.MongoDB.devicesCollection.count_documents({"type": "Device", "category.value.0": "Server"}), + "Camera": self.MongoDB.devicesCollection.count_documents({"type": "Device", "category.value.0": "Camera"}), + "Scanner": self.MongoDB.devicesCollection.count_documents({"type": "Device", "category.value.0": "Scanner"}), + "Virtual Reality": self.MongoDB.devicesCollection.count_documents({"type": "Device", "category.value.0": "Virtual Reality"}), + "Mixed Reality": self.MongoDB.devicesCollection.count_documents({"type": "Device", "category.value.0": "Mixed Reality"}), + "Robotics": { + "EMAR": self.MongoDB.devicesCollection.count_documents({"type": "Device", "category.value.0": "EMAR"}) + }, + "AI": { + "GeniSysAI": self.MongoDB.devicesCollection.count_documents({"type": "Device", "category.value.0": "GeniSysAI"}), + "TassAI": self.MongoDB.devicesCollection.count_documents({"type": "Device", "category.value.0": "TassAI"}), + "AML": self.MongoDB.devicesCollection.count_documents({"type": "Device", "category.value.0": "AMLClassifier"}), + "ALL": self.MongoDB.devicesCollection.count_documents({"type": "Device", "category.value.0": "ALLClassifier"}), + "COVID": self.MongoDB.devicesCollection.count_documents({"type": "Device", "category.value.0": "COVIDClassifier"}), + "Skin": self.MongoDB.devicesCollection.count_documents({"type": "Device", "category.value.0": "SkinCancerClassifier"}), + } + } + } + } + + def respond(self, responseCode, response, location=None): + """ Builds the request repsonse """ + + return Response(response=json.dumps(response, indent=4), status=responseCode, + mimetype="application/json") + + def signal_handler(self, signal, frame): + self.Helpers.logger.info("Disconnecting") + sys.exit(1) + + +app = Flask(__name__) +ContextBroker = ContextBroker() + +@app.route('/about', methods=['GET']) +def about(): + """ Responds to GET requests sent to the /v1/about API endpoint. """ + + return ContextBroker.respond(200, { + "Response": "OK", + "Data": ContextBroker.getBroker() + }) + +@app.route('/entities', methods=['POST']) +def entitiesPost(): + """ Responds to POST requests sent to the /v1/entities API endpoint. """ + + if request.headers["Content-Type"] == "application/json": + query = request.json + if query["id"] is None: + return ContextBroker.Entities.respond(400, { + "Response": "Failed", + "Error": "BadRequest", + "Description": "Entity ID required!" + }) + return ContextBroker.Entities.createEntity(query) + else: + return ContextBroker.Entities.respond(405, { + "Response": "Failed", + "Error": "MethodNotAlowed", + "Description": "Method not allowed!" + }) + +@app.route('/entities', methods=['GET']) +def entitiesGet(): + """ Responds to GET requests sent to the /v1/entities API endpoint. """ + + if request.args.get('type') is None: + return ContextBroker.Entities.respond(400, { + "Response": "Failed", + "Error": "BadRequest", + "Description": "Entity type required!" + }) + if request.args.get('limit') is None: + limit = 0; + else: + limit = int(request.args.get('limit')) + if request.args.get('values') is None: + values = None + else: + values = request.args.get('values') + return ContextBroker.Entities.getEntities(request.args.get('type'), + limit, request.args.get('category'), values) + +@app.route('/entities/<_id>', methods=['GET']) +def entityGet(_id): + """ Responds to GET requests sent to the /v1/entities/<_id> API endpoint. """ + + if request.args.get('type') is None: + return ContextBroker.Entities.respond(400, { + "Response": "Failed", + "Error": "BadRequest", + "Description": "Entity type required!" + }) + if request.args.get('attrs') is None: + attrs = None + else: + attrs = request.args.get('attrs') + return ContextBroker.Entities.getEntity(request.args.get('type'), _id, attrs) + +@app.route('/entities/<_id>/attrs', methods=['PATCH']) +def entitiesUpdate(_id): + """ Responds to PATCH requests sent to the /v1/entities/<_id>/attrs API endpoint. """ + + if request.headers["Content-Type"] == "application/json": + query = request.json + if request.args.get('type') is None: + return ContextBroker.Entities.respond(400, { + "Response": "Failed", + "Error": "BadRequest", + "Description": "Entity ID required!" + }) + return ContextBroker.Entities.updateEntity(_id, request.args.get('type'), query) + else: + return ContextBroker.Entities.respond(405, { + "Response": "Failed", + "Error": "MethodNotAlowed", + "Description": "Method not allowed!" + }) + +@app.route('/entities/<_id>', methods=['DELETE']) +def entityDelete(_id): + """ Responds to DELETE requests sent to the /v1/entities/<_id> API endpoint. """ + + if _id is None: + return ContextBroker.Entities.respond(400, { + "Response": "Failed", + "Error": "BadRequest", + "Description": "Entity ID required!" + }) + if request.args.get('type') is None: + return ContextBroker.Entities.respond(400, { + "Response": "Failed", + "Error": "BadRequest", + "Description": "Entity type required!" + }) + return ContextBroker.Entities.deleteEntity(request.args.get('type'), _id) + +@app.route('/agents', methods=['POST']) +def agentsPost(): + """ Responds to POST requests sent to the /v1/agents API endpoint. """ + + if request.headers["Content-Type"] == "application/json": + query = request.json + if query["id"] is None: + return ContextBroker.Agents.respond(400, { + "Response": "Failed", + "Error": "BadRequest", + "Description": "Agent ID required!" + }) + return ContextBroker.Agents.createAgent(query) + else: + return ContextBroker.Agents.respond(405, { + "Response": "Failed", + "Error": "MethodNotAlowed", + "Description": "Method not allowed!" + }) + +@app.route('/agents', methods=['GET']) +def agentsGet(): + """ Responds to GET requests sent to the /v1/agents API endpoint. """ + + if request.args.get('limit') is None: + limit = 0; + else: + limit = int(request.args.get('limit')) + return ContextBroker.Agents.getAgents(limit) + +@app.route('/agents/<_id>', methods=['GET']) +def agentGet(_id): + """ Responds to GET requests sent to the /v1/agents/<_id> API endpoint. """ + + if request.args.get('attrs') is None: + attrs = None + else: + attrs = request.args.get('attrs') + return ContextBroker.Agents.getAgent(_id, attrs) + +@app.route('/agents/<_id>/attrs', methods=['PATCH']) +def agentUpdate(_id): + """ Responds to PATCH requests sent to the /v1/agents/<_id>/attrs API endpoint. """ + + if request.headers["Content-Type"] == "application/json": + data = request.json + return ContextBroker.Agents.updateAgent(_id, data) + else: + return ContextBroker.Agents.respond(405, { + "Response": "Failed", + "Error": "MethodNotAlowed", + "Description": "Method not allowed!" + }) + +@app.route('/agents/<_id>', methods=['DELETE']) +def agentDelete(_id): + """ Responds to DELETE requests sent to the /v1/agents/<_id> API endpoint. """ + + if _id is None: + return ContextBroker.Agents.respond(400, { + "Response": "Failed", + "Error": "BadRequest", + "Description": "Agent ID required!" + }) + return ContextBroker.Agents.deleteAgent(_id) + +def main(): + signal.signal(signal.SIGINT, ContextBroker.signal_handler) + signal.signal(signal.SIGTERM, ContextBroker.signal_handler) + + ContextBroker.mongoDbConnection() + ContextBroker.iotConnection() + ContextBroker.configureBroker() + + Thread(target=ContextBroker.life, args=(), daemon=True).start() + + app.run(host=ContextBroker.Helpers.confs["iotJumpWay"]["ip"], + port=ContextBroker.Helpers.confs["iotJumpWay"]["ContextBroker"]["port"]) + +if __name__ == "__main__": + main() diff --git a/Context/Entities.py b/Context/Entities.py new file mode 100644 index 0000000..1cb1850 --- /dev/null +++ b/Context/Entities.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python3 +###################################################################################################### +# +# Organization: Peter Moss Leukemia AI Research +# Repository: HIAS: Hospital Intelligent Automation System +# +# Author: Adam Milton-Barker (AdamMiltonBarker.com) +# +# Title: HIAS HDSI Context Broker +# Description: The HIAS HDSI Context Broker handles contextual data for all iotJumpWay +# devices/applications and IoT Agents. The Context Broker uses the HDSI (HIAS +# Data Services Interface) API V1, based on Open Mobile Alliance's NGSI V2. +# License: MIT License +# Last Modified: 2020-10-18 +# +###################################################################################################### + +import json +import jsonpickle +import os +import sys + +from bson import json_util, ObjectId +from flask import Response + +from Classes.Helpers import Helpers + + +class Entities(): + """ Entities Class + + Handles Entity functionality for the HIAS HDSI Context Broker. + """ + + def __init__(self, mongoConnection): + """ Initializes the class. """ + + self.Helpers = Helpers("Entities") + self.MongoDB = mongoConnection + self.Helpers.logger.info("Entities Class initialization complete.") + + def createEntity(self, data): + """ Creates a new NDSI Entity """ + + if data["type"] == "Location": + collection = self.MongoDB.locationsCollection + elif data["type"] == "Zone": + collection = self.MongoDB.zonesCollection + elif data["type"] == "Device": + collection = self.MongoDB.devicesCollection + elif data["type"] == "Application": + collection = self.MongoDB.applicationsCollection + elif data["type"] == "Patient": + collection = self.MongoDB.patientsCollection + elif data["type"] == "Staff": + collection = self.MongoDB.staffCollection + elif data["type"] == "Thing": + collection = self.MongoDB.thingsCollection + elif data["type"] == "Model": + collection = self.MongoDB.modelsCollection + else: + return self.respond(400, { + "Response": "Failed", + "Error": "BadRequest", + "Description": "Entity type not supported!" + }) + + _id = self.MongoDB.insertData(collection, data, data["type"]) + + resp = { + "Response": "OK", + "ID": str(_id), + "Entity": json.loads(json_util.dumps(data)) + } + + if str(_id) is not False: + return self.respond(201, resp, "v1/entities/" + data["id"] + "?type=" + data["type"]) + else: + return self.respond(400, { + "Response": "Failed", + "Error": "BadRequest", + "Description": "Entity storage failed!" + }) + + def getEntities(self, typeof, limit=0, category=None, values=None): + """ Gets all NDSI Entities """ + + if typeof == "Location": + collection = self.MongoDB.locationsCollection + elif typeof == "Zone": + collection = self.MongoDB.zonesCollection + elif typeof == "Device": + collection = self.MongoDB.devicesCollection + elif typeof == "Application": + collection = self.MongoDB.applicationsCollection + elif typeof == "Patient": + collection = self.MongoDB.patientsCollection + elif typeof == "Staff": + collection = self.MongoDB.staffCollection + elif typeof == "Thing": + collection = self.MongoDB.thingsCollection + elif typeof == "Model": + collection = self.MongoDB.modelsCollection + else: + return self.respond(400, { + "Response": "Failed", + "Error": "BadRequest", + "Description": "Entity type not supported!" + }) + + entities = self.MongoDB.getData(collection, limit, category, values) + + if not entities: + return self.respond(404, { + "Response": "Failed", + "Error": "NotFound", + "Description": "No entities exist for this type!" + }) + else: + resp = { + "Response": "OK", + "Data": json.loads(json_util.dumps(entities)) + } + return self.respond(200, resp) + + def getEntity(self, typeof, _id, attrs): + """ Gets a specific NDSI Entity """ + + if typeof == "Location": + collection = self.MongoDB.locationsCollection + elif typeof == "Zone": + collection = self.MongoDB.zonesCollection + elif typeof == "Device": + collection = self.MongoDB.devicesCollection + elif typeof == "Application": + collection = self.MongoDB.applicationsCollection + elif typeof == "Patient": + collection = self.MongoDB.patientsCollection + elif typeof == "Staff": + collection = self.MongoDB.staffCollection + elif typeof == "Thing": + collection = self.MongoDB.thingsCollection + elif typeof == "Model": + collection = self.MongoDB.modelsCollection + else: + return self.respond(400, { + "Response": "Failed", + "Error": "BadRequest", + "Description": "Entity type not supported!" + }) + + entity = self.MongoDB.getDataById(collection, _id, attrs) + + if not entity: + return self.respond(404, { + "Response": "Failed", + "Error": "NotFound", + "Description": "Entity does not exist!" + }) + else: + resp = { + "Response": "OK", + "Data": json.loads(json_util.dumps(entity[0])) + } + return self.respond(200, resp) + + def updateEntity(self, _id, typeof, data): + """ Updates an NDSI Entity """ + + if typeof == "Location": + collection = self.MongoDB.locationsCollection + elif typeof == "Zone": + collection = self.MongoDB.zonesCollection + elif typeof == "Device": + collection = self.MongoDB.devicesCollection + elif typeof == "Application": + collection = self.MongoDB.applicationsCollection + elif typeof == "Patient": + collection = self.MongoDB.patientsCollection + elif typeof == "Staff": + collection = self.MongoDB.staffCollection + elif typeof == "Thing": + collection = self.MongoDB.thingsCollection + elif typeof == "Model": + collection = self.MongoDB.modelsCollection + else: + return self.respond(400, { + "Response": "Failed", + "Error": "BadRequest", + "Description": "Entity type not supported!" + }) + + updated = self.MongoDB.updateData(_id, collection, data) + + if updated is True: + return self.respond(200, { + "Response": "OK" + }) + else: + return self.respond(400, { + "Response": "OK", + "Error": "BadRequest", + "Description": "Entity update failed!" + }) + + def deleteEntity(self, typeof, _id): + """ Deletes an NDSI Entity """ + + if typeof == "Location": + collection = self.MongoDB.locationsCollection + elif typeof == "Zone": + collection = self.MongoDB.zonesCollection + elif typeof == "Device": + collection = self.MongoDB.devicesCollection + elif typeof == "Application": + collection = self.MongoDB.applicationsCollection + elif typeof == "Patient": + collection = self.MongoDB.patientsCollection + elif typeof == "Staff": + collection = self.MongoDB.staffCollection + elif typeof == "Thing": + collection = self.MongoDB.thingsCollection + elif typeof == "Model": + collection = self.MongoDB.modelsCollection + else: + return self.respond(400, { + "Response": "Failed", + "Error": "BadRequest", + "Description": "Entity type not supported!" + }) + + updated = self.MongoDB.deleteData(_id, collection) + + if updated is True: + return self.respond(200, { + "Response": "OK" + }) + else: + return self.respond(400, { + "Response": "OK", + "Error": "BadRequest", + "Description": "Entity update failed!" + }) + + def respond(self, responseCode, response, location=None): + """ Builds the request repsonse """ + + return Response(response=json.dumps(response, indent=4), status=responseCode, + mimetype="application/json") From a9f9c7af0af9427da441d9359a42bc063061b28a Mon Sep 17 00:00:00 2001 From: AdamMiltonBarker Date: Thu, 22 Oct 2020 00:57:26 +0200 Subject: [PATCH 06/22] 2.0.0 IoT Agents (MQTT & AMQP) --- Agents/AMQP.py | 374 ++++++++++++++++++++++++++ Agents/MQTT.py | 695 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1069 insertions(+) create mode 100644 Agents/AMQP.py create mode 100644 Agents/MQTT.py diff --git a/Agents/AMQP.py b/Agents/AMQP.py new file mode 100644 index 0000000..ee9314d --- /dev/null +++ b/Agents/AMQP.py @@ -0,0 +1,374 @@ +#!/usr/bin/env python3 +###################################################################################################### +# +# Organization: Peter Moss Leukemia AI Research +# Repository: HIAS: Hospital Intelligent Automation System +# +# Author: Adam Milton-Barker (AdamMiltonBarker.com) +# +# Title: iotJumpWay AMQP IoT Agent +# Description: The AMQP IoT Agent listens for all traffic coming from devices connected to the HIAS +# HIAS network using the AMQP protocol, translates them into a format compatible with +# the HIAS iotJumpWay Context Broker and sends the data to the broker for processing +# and storage. +# License: MIT License +# Last Modified: 2020-10-18 +# +###################################################################################################### + + +import os +import sys +import time + +sys.path.insert(0, os.path.abspath( + os.path.join(os.path.dirname(__file__), '..'))) + +from gevent import monkey +monkey.patch_all() + +import json +import pika +import psutil +import requests +import signal +import ssl +import threading + +from datetime import timedelta +from datetime import datetime +from flask import Flask, request, Response +from threading import Thread + +from Classes.Helpers import Helpers +from Classes.Blockchain import Blockchain +from Classes.ContextBroker import ContextBroker +from Classes.MongoDB import MongoDB + + +class AMQP(): + """ iotJumpWay AMQP IoT Agent + + The AMQP IoT Agent listens for all traffic coming from devices + connected to the HIAS network using the AMQP protocol. + """ + + def __init__(self): + """ Initializes the class. """ + + self.Helpers = Helpers("AMQP") + self.Helpers.logger.info("AMQP Agent initialization complete.") + + def amqpConnect(self): + """ Initiates the AMQP connection. """ + + credentials = self.Helpers.confs["iotJumpWay"]["AMQP"]["un"] + \ + ':' + self.Helpers.confs["iotJumpWay"]["AMQP"]["pw"] + parameters = pika.URLParameters( + 'amqps://' + credentials + '@' + self.Helpers.confs["iotJumpWay"]["host"] + '/' + self.Helpers.confs["iotJumpWay"]["AMQP"]["vhost"]) + context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) + context.load_verify_locations(self.Helpers.confs["iotJumpWay"]["cert"]) + parameters.ssl_options = pika.SSLOptions(context) + + self.connection = pika.BlockingConnection(parameters) + self.channel = self.connection.channel() + self.amqpPublish(json.dumps({"Application": self.Helpers.confs["iotJumpWay"]["AMQP"]["identifier"], + "Status": "ONLINE"}), "Statuses") + self.Helpers.logger.info("AMQP connection established!") + + def blockchainConnect(self): + """ Initiates the Blockchain connection. """ + + self.Blockchain = Blockchain() + self.Blockchain.startBlockchain() + self.Blockchain.w3.geth.personal.unlockAccount( + self.Helpers.confs["ethereum"]["haddress"], self.Helpers.confs["ethereum"]["hpass"], 0) + self.Blockchain.w3.geth.personal.unlockAccount( + self.Helpers.confs["ethereum"]["iaddress"], self.Helpers.confs["ethereum"]["ipass"], 0) + + def mongoDbConn(self): + """ Initiates the MongoDB connection. """ + + self.MongoDB = MongoDB() + self.MongoDB.startMongoDB() + + def amqpConsumeSet(self): + """ Sets up the AMQP queue subscriptions. """ + + self.channel.basic_consume('Life', + self.lifeCallback, + auto_ack=True) + self.channel.basic_consume('Statuses', + self.statusesCallback, + auto_ack=True) + self.Helpers.logger.info("AMQP consume setup!") + + def amqpConsumeStart(self): + """ Starts consuming. """ + + self.Helpers.logger.info("AMQP consume starting!") + self.channel.start_consuming() + + def amqpPublish(self, data, routing_key): + """ Publishes to an AMQP broker queue. """ + + self.channel.basic_publish( + exchange=self.Helpers.confs["iotJumpWay"]["AMQP"]["exchange"], routing_key=routing_key, body=data) + self.Helpers.logger.info("AMQP consume setup!") + + def life(self): + """ Sends vital statistics to HIAS. """ + + cpu = psutil.cpu_percent() + mem = psutil.virtual_memory()[2] + hdd = psutil.disk_usage('/').percent + tmp = psutil.sensors_temperatures()['coretemp'][0].current + r = requests.get('http://ipinfo.io/json?token=' + + self.Helpers.confs["iotJumpWay"]["ipinfo"]) + data = r.json() + location = data["loc"].split(',') + + self.amqpPublish(json.dumps({ + "Application": self.Helpers.confs["iotJumpWay"]["AMQP"]["identifier"], + "CPU": str(cpu), + "Memory": str(mem), + "Diskspace": str(hdd), + "Temperature": str(tmp), + "Latitude": float(location[0]), + "Longitude": float(location[1]) + }), "Life") + + self.Helpers.logger.info("Agent life statistics published.") + threading.Timer(300.0, self.life).start() + + def contextConn(self): + """ Initiates the Context Broker class. """ + + self.ContextBroker = ContextBroker() + + def statusesCallback(self, ch, method, properties, body): + """ Processes status messages. """ + Thread(target=self.statusesWorker, args=(body,), daemon=True).start() + + def statusesWorker(self, body): + """ Processes status messages. """ + + self.Helpers.logger.info("Life data callback") + data = json.loads(body) + + if "Application" in data: + entityType = "Application" + entity = data["Application"] + application = entity + zone = "NA" + device = "NA" + short = "App" + del data['Application'] + elif "Device" in data: + entityType = "Device" + entity = data["Device"] + application = "NA" + device = entity + short = "Device" + del data['Device'] + + status = data['Status'] + + requiredAttributes = self.ContextBroker.getRequiredAttributes( + entity, entityType) + + locationID = int(requiredAttributes["Data"]["lid"]["value"]) + location = requiredAttributes["Data"]["lid"]["entity"] + if entityType is "Device": + zone = requiredAttributes["Data"]["zid"]["entity"] + bcAddress = requiredAttributes["Data"]["blockchain"]["address"] + + if not self.Blockchain.iotJumpWayAccessCheck(bcAddress): + return + + updateResponse = self.ContextBroker.updateEntity( + entity, entityType, { + "status": { + "value": status, + "timestamp": datetime.now().isoformat() + } + }) + + if updateResponse["Response"] == "OK": + self.Helpers.logger.info(entityType + " " + entity + " status update OK") + _id = self.MongoDB.insertData(self.MongoDB.mongoConn.Statuses, { + "Use": entityType, + "Location": location, + "Zone": zone, + "Application": application, + "Device": device, + "Status": status, + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }, None) + Thread(target=self.Blockchain.storeHash, args=(str(_id), self.Blockchain.hashStatus(status), int(time.time()), + locationID, entity, bcAddress, short), daemon=True).start() + else: + self.Helpers.logger.error( + entityType + " " + entity + " status update KO") + + def lifeCallback(self, ch, method, properties, body): + """ Processes life messages. """ + Thread(target=self.lifeWorker, args=(body,), daemon=True).start() + + def lifeWorker(self, body): + + self.Helpers.logger.info("Life data callback") + data = json.loads(body) + + if "Application" in data: + entityType = "Application" + entity = data["Application"] + application = entity + zone = "NA" + device = "NA" + short = "App" + del data['Application'] + elif "Device" in data: + entityType = "Device" + entity = data["Device"] + application = "NA" + device = entity + short = "Device" + del data['Device'] + + requiredAttributes = self.ContextBroker.getRequiredAttributes( + entity, entityType) + + locationID = int(requiredAttributes["Data"]["lid"]["value"]) + location = requiredAttributes["Data"]["lid"]["entity"] + if entityType is "Device": + zone = requiredAttributes["Data"]["zid"]["entity"] + bcAddress = requiredAttributes["Data"]["blockchain"]["address"] + + if not self.Blockchain.iotJumpWayAccessCheck(bcAddress): + return + + updateResponse = self.ContextBroker.updateEntity( + entity, entityType, { + "status": { + "value": "ONLINE", + "timestamp": datetime.now().isoformat() + }, + "cpuUsage": { + "value": data["CPU"] + }, + "memoryUsage": { + "value": data["Memory"] + }, + "hddUsage": { + "value": data["Diskspace"] + }, + "temperature": { + "value": data["Temperature"] + }, + "location": { + "type": "geo:json", + "value": { + "type": "Point", + "coordinates": [float(data["Latitude"]), float(data["Longitude"])] + } + } + }) + + if updateResponse["Response"] == "OK": + self.Helpers.logger.info(entityType + " " + entity + " status update OK") + _id = self.MongoDB.insertData(self.MongoDB.mongoConn.Life, { + "Use": "Application", + "Location": location, + "Zone": zone, + "Application": application, + "Device": device, + "Data": data, + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }, None) + Thread(target=self.Blockchain.storeHash, args=(str(_id), self.Blockchain.hashLifeData(data), int(time.time()), + locationID, entity, bcAddress, short), daemon=True).start() + else: + self.Helpers.logger.error( + entityType + " " + entity + " status update KO") + + def agentThreads(self): + """ Processes status messages. """ + + Thread(target=AMQP.life, args=(), daemon=True).start() + Thread(target=AMQP.amqpConsumeStart, args=(), daemon=True).start() + + def respond(self, responseCode, response): + """ Builds the request repsonse """ + + return Response(response=json.dumps(response, indent=4), status=responseCode, + mimetype="application/json") + + def signal_handler(self, signal, frame): + self.Helpers.logger.info("Disconnecting") + self.amqpPublish(json.dumps({"Application": self.Helpers.confs["iotJumpWay"]["AMQP"]["identifier"], + "Status": "OFFLINE"}), "Statuses") + self.connection.close() + sys.exit(1) + + +app = Flask(__name__) +AMQP = AMQP() + + +@app.route('/About', methods=['GET']) +def about(): + """ Responds to POST requests sent to the North Port About API endpoint. """ + + return AMQP.respond(200, { + "Identifier": AMQP.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["identifier"], + "Host": AMQP.Helpers.confs["iotJumpWay"]["ip"], + "NorthPort": AMQP.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["northPort"], + "CPU": psutil.cpu_percent(), + "Memory": psutil.virtual_memory()[2], + "Diskspace": psutil.disk_usage('/').percent, + "Temperature": psutil.sensors_temperatures()['coretemp'][0].current + }) + + +@app.route('/Commands', methods=['POST']) +def commands(): + """ Responds to POST requests sent to the North Port Commands API endpoint. """ + + if request.headers["Content-Type"] == "application/json": + command = request.json + if command["ToType"] is "Device": + print("Device Command") + elif command["ToType"] is "Application": + print("Application Command") + else: + return AMQP.respond(400, { + "Response": "Failed", + "Error": "BadRequest", + "Description": "Command type not supported!" + }) + else: + return AMQP.respond(405, { + "Response": "Failed", + "Error": "MethodNotAlowed", + "Description": "Method not allowed!" + }) + +def main(): + + signal.signal(signal.SIGINT, AMQP.signal_handler) + signal.signal(signal.SIGTERM, AMQP.signal_handler) + + # Starts the IoT Agent + AMQP.contextConn() + AMQP.mongoDbConn() + AMQP.blockchainConnect() + AMQP.amqpConnect() + AMQP.amqpConsumeSet() + AMQP.agentThreads() + + app.run(host=AMQP.Helpers.confs["iotJumpWay"]["ip"], + port=AMQP.Helpers.confs["iotJumpWay"]["AMQP"]["northPort"]) + +if __name__ == "__main__": + main() diff --git a/Agents/MQTT.py b/Agents/MQTT.py new file mode 100644 index 0000000..e71dec6 --- /dev/null +++ b/Agents/MQTT.py @@ -0,0 +1,695 @@ +#!/usr/bin/env python3 +###################################################################################################### +# +# Organization: Peter Moss Leukemia AI Research +# Repository: HIAS: Hospital Intelligent Automation System +# +# Author: Adam Milton-Barker (AdamMiltonBarker.com) +# +# Title: iotJumpWay MQTT IoT Agent +# Description: The MQTT IoT Agent listens for all traffic coming from devices connected to the HIAS +# HIAS network using the MQTT & Websocket protocols, translates them into a format +# compatible with the HIAS iotJumpWay Context Broker and sends the data to the broker +# for processing and storage. +# License: MIT License +# Last Modified: 2020-10-13 +# +###################################################################################################### + +import json +import os +import psutil +import requests +import signal +import sys +import time +import threading + +sys.path.insert(0, os.path.abspath( + os.path.join(os.path.dirname(__file__), '..'))) + +from datetime import datetime +from datetime import timedelta + +from flask import Flask, request, Response +from threading import Thread + +from web3 import Web3 + +from Classes.Helpers import Helpers +from Classes.ContextBroker import ContextBroker +from Classes.Blockchain import Blockchain +from Classes.MQTT import Application +from Classes.MongoDB import MongoDB + + +class MQTT(): + """ iotJumpWay MQTT IoT Agent + + The MQTT IoT Agent listens for all traffic coming from devices + connected to the HIAS network using the MQTT protocol. + """ + + def __init__(self): + """ Initializes the class. """ + + self.Helpers = Helpers("MQTT") + self.Helpers.logger.info("MQTT Agent initialization complete.") + + def startIoT(self): + """ Initiates the iotJumpWay connection. """ + + self.Application = Application({ + "host": self.Helpers.confs["iotJumpWay"]["host"], + "port": self.Helpers.confs["iotJumpWay"]["MQTT"]["port"], + "lid": self.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["lid"], + "aid": self.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["aid"], + "an": self.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["an"], + "un": self.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["un"], + "pw": self.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["pw"] + }) + self.Application.connect() + + self.Application.appChannelSub("#", "#") + self.Application.appDeviceChannelSub("#", "#", "#") + + self.Application.appLifeCallback = self.appLifeCallback + self.Application.appSensorCallback = self.appSensorCallback + self.Application.appStatusCallback = self.appStatusCallback + self.Application.deviceCommandsCallback = self.deviceCommandsCallback + self.Application.deviceNfcCallback = self.deviceNfcCallback + self.Application.deviceSensorCallback = self.deviceSensorCallback + self.Application.deviceStatusCallback = self.deviceStatusCallback + self.Application.deviceLifeCallback = self.deviceLifeCallback + + self.Helpers.logger.info("iotJumpWay connection initiated.") + + def contextConn(self): + """ Initiates the Context Broker class. """ + + self.ContextBroker = ContextBroker() + + def mongoDbConn(self): + """ Initiates the MongoDB connection. """ + + self.MongoDB = MongoDB() + self.MongoDB.startMongoDB() + + def blockchainConn(self): + """ Initiates the Blockchain connection. """ + + self.Blockchain = Blockchain() + self.Blockchain.startBlockchain() + self.Blockchain.w3.geth.personal.unlockAccount( + self.Helpers.confs["ethereum"]["haddress"], self.Helpers.confs["ethereum"]["hpass"], 0) + self.Blockchain.w3.geth.personal.unlockAccount( + self.Helpers.confs["ethereum"]["iaddress"], self.Helpers.confs["ethereum"]["ipass"], 0) + + def life(self): + """ Sends vital statistics to HIAS """ + + cpu = psutil.cpu_percent() + mem = psutil.virtual_memory()[2] + hdd = psutil.disk_usage('/').percent + tmp = psutil.sensors_temperatures()['coretemp'][0].current + r = requests.get('http://ipinfo.io/json?token=' + + self.Helpers.confs["iotJumpWay"]["ipinfo"]) + data = r.json() + location = data["loc"].split(',') + + self.Application.appChannelPub("Life", self.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["aid"], { + "CPU": str(cpu), + "Memory": str(mem), + "Diskspace": str(hdd), + "Temperature": str(tmp), + "Latitude": float(location[0]), + "Longitude": float(location[1]) + }) + + self.Helpers.logger.info("Agent life statistics published.") + threading.Timer(300.0, self.life).start() + + def appStatusCallback(self, topic, payload): + """ + iotJumpWay Application Status Callback + + The callback function that is triggered in the event of status + communication via MQTT from an iotJumpWay application. + """ + + self.Helpers.logger.info( + "Recieved iotJumpWay Application Status: " + payload.decode()) + + splitTopic = topic.split("/") + status = payload.decode() + + location = splitTopic[0] + application = splitTopic[2] + + requiredAttributes = self.ContextBroker.getRequiredAttributes( + application, "Application") + + locationID = int(requiredAttributes["Data"]["lid"]["value"]) + bcAddress = requiredAttributes["Data"]["blockchain"]["address"] + + if not self.Blockchain.iotJumpWayAccessCheck(bcAddress): + return + + updateResponse = self.ContextBroker.updateEntity( + application, "Application", { + "status": { + "value": status, + "timestamp": datetime.now().isoformat() + } + }) + + if updateResponse["Response"] == "OK": + self.Helpers.logger.info("Application " + application + " status update OK") + _id = self.MongoDB.insertData(self.MongoDB.mongoConn.Statuses, { + "Use": "Application", + "Location": location, + "Zone": "NA", + "Application": application, + "Device": "NA", + "Status": status, + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }, None) + Thread(target=self.Blockchain.storeHash, args=(str(_id), self.Blockchain.hashStatus(status), int(time.time()), + locationID, application, bcAddress, "App"), daemon=True).start() + else: + self.Helpers.logger.error("Application " + application + " status update KO") + + def appLifeCallback(self, topic, payload): + """ + iotJumpWay Application Life Callback + + The callback function that is triggered in the event of life + communication via MQTT from an iotJumpWay application. + """ + + self.Helpers.logger.info( + "Recieved iotJumpWay Application Life Data: " + payload.decode()) + + data = json.loads(payload.decode("utf-8")) + splitTopic = topic.split("/") + + location = splitTopic[0] + application = splitTopic[2] + + requiredAttributes = self.ContextBroker.getRequiredAttributes( + application, "Application") + + locationID = int(requiredAttributes["Data"]["lid"]["value"]) + bcAddress = requiredAttributes["Data"]["blockchain"]["address"] + + if not self.Blockchain.iotJumpWayAccessCheck(bcAddress): + return + + updateResponse = self.ContextBroker.updateEntity( + application, "Application", { + "status": { + "value": "ONLINE", + "timestamp": datetime.now().isoformat() + }, + "cpuUsage": { + "value": data["CPU"] + }, + "memoryUsage": { + "value": data["Memory"] + }, + "hddUsage": { + "value": data["Diskspace"] + }, + "temperature": { + "value": data["Temperature"] + }, + "location": { + "type": "geo:json", + "value": { + "type": "Point", + "coordinates": [float(data["Latitude"]), float(data["Longitude"])] + } + } + }) + + if updateResponse["Response"] == "OK": + _id = self.MongoDB.insertData(self.MongoDB.mongoConn.Life, { + "Use": "Application", + "Location": location, + "Zone": "NA", + "Application": application, + "Device": "NA", + "Data": data, + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }, None) + Thread(target=self.Blockchain.storeHash, args=(str(_id), self.Blockchain.hashLifeData(data), int(time.time()), + locationID, application, bcAddress, "App"), daemon=True).start() + self.Helpers.logger.info("Application " + application + " status update OK") + else: + self.Helpers.logger.error("Application " + application + " life update KO") + + def appSensorCallback(self, topic, payload): + """ + iotJumpWay Application Sensors Callback + + The callback function that is triggered in the event of sensor + communication via MQTT from an iotJumpWay application. + """ + + self.Helpers.logger.info( + "Recieved iotJumpWay Application Sensors Data: " + payload.decode()) + + data = json.loads(payload.decode("utf-8")) + splitTopic = topic.split("/") + + location = splitTopic[0] + application = splitTopic[2] + + requiredAttributes = self.ContextBroker.getRequiredAttributes( + application, "Application") + + locationID = int(requiredAttributes["Data"]["lid"]["value"]) + bcAddress = requiredAttributes["Data"]["blockchain"]["address"] + + if not self.Blockchain.iotJumpWayAccessCheck(bcAddress): + return + + updateResponse = self.ContextBroker.updateEntity( + application, "Application", { + "status": { + "value": "ONLINE", + "timestamp": datetime.now().isoformat() + }, + "cpuUsage": { + "value": data["CPU"] + }, + "memoryUsage": { + "value": data["Memory"] + }, + "hddUsage": { + "value": data["Diskspace"] + }, + "temperature": { + "value": data["Temperature"] + }, + "location": { + "type": "geo:json", + "value": { + "type": "Point", + "coordinates": [float(data["Latitude"]), float(data["Longitude"])] + } + } + }) + + if updateResponse["Response"] == "OK": + _id = self.MongoDB.insertData(self.MongoDB.mongoConn.Life, { + "Use": "Application", + "Location": location, + "Zone": "NA", + "Application": application, + "Device": "NA", + "Data": data, + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }, None) + Thread(target=self.Blockchain.storeHash, args=(str(_id), self.Blockchain.hashLifeData(data), int(time.time()), + locationID, application, bcAddress, "App"), daemon=True).start() + self.Helpers.logger.info("Application " + application + " status update OK") + else: + self.Helpers.logger.error("Application " + application + " life update KO") + + data = json.loads(payload.decode("utf-8")) + splitTopic = topic.split("/") + + location = splitTopic[0] + application = splitTopic[2] + + requiredAttributes = self.ContextBroker.getRequiredAttributes( + application, "Application") + + locationID = int(requiredAttributes["Data"]["lid"]["value"]) + bcAddress = requiredAttributes["Data"]["blockchain"]["address"] + + if not self.Blockchain.iotJumpWayAccessCheck(bcAddress): + return + + self.MongoDB.insertData(self.MongoDB.mongoConn.Sensors, { + "Use": "Application", + "Location": splitTopic[0], + "Zone": 0, + "Application": splitTopic[2], + "Device": 0, + "Sensor": data["Sensor"], + "Type": data["Type"], + "Value": data["Value"], + "Message": data["Message"], + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }, None) + + def deviceStatusCallback(self, topic, payload): + """ + iotJumpWay Device Status Callback + + The callback function that is triggered in the event of status + communication via MQTT from an iotJumpWay device. + """ + + self.Helpers.logger.info( + "Recieved iotJumpWay Device Status: " + payload.decode()) + + splitTopic = topic.split("/") + status = payload.decode() + + location = splitTopic[0] + zone = splitTopic[2] + device = splitTopic[3] + + requiredAttributes = self.ContextBroker.getRequiredAttributes( + device, "Device") + + locationID = int(requiredAttributes["Data"]["lid"]["value"]) + bcAddress = requiredAttributes["Data"]["blockchain"]["address"] + + if not self.Blockchain.iotJumpWayAccessCheck(bcAddress): + return + + updateResponse = self.ContextBroker.updateEntity( + device, "Device", { + "status": { + "value": status, + "timestamp": datetime.now().isoformat() + } + }) + + if updateResponse["Response"] == "OK": + self.Helpers.logger.info("Device " + device + " status update OK") + _id = self.MongoDB.insertData(self.MongoDB.mongoConn.Statuses, { + "Use": "Device", + "Location": location, + "Zone": zone, + "Application": "NA", + "Device": device, + "Status": status, + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }, None) + Thread(target=self.Blockchain.storeHash, args=(str(_id), self.Blockchain.hashStatus(status), int(time.time()), + locationID, device, bcAddress, "Device"), daemon=True).start() + else: + self.Helpers.logger.error("Device " + device + " status update KO") + + def deviceLifeCallback(self, topic, payload): + """ + iotJumpWay Device Life Callback + + The callback function that is triggered in the event of life + communication via MQTT from an iotJumpWay device. + """ + + self.Helpers.logger.info( + "Recieved iotJumpWay Device Life Data: " + payload.decode()) + + data = json.loads(payload.decode("utf-8")) + splitTopic = topic.split("/") + + location = splitTopic[0] + zone = splitTopic[2] + device = splitTopic[3] + + requiredAttributes = self.ContextBroker.getRequiredAttributes( + device, "Device") + + locationID = int(requiredAttributes["Data"]["lid"]["value"]) + bcAddress = requiredAttributes["Data"]["blockchain"]["address"] + + if not self.Blockchain.iotJumpWayAccessCheck(bcAddress): + return + + updateResponse = self.ContextBroker.updateEntity( + device, "Device", { + "status": { + "value": "ONLINE", + "timestamp": datetime.now().isoformat() + }, + "cpuUsage": { + "value": data["CPU"] + }, + "memoryUsage": { + "value": data["Memory"] + }, + "hddUsage": { + "value": data["Diskspace"] + }, + "temperature": { + "value": data["Temperature"] + }, + "location": { + "type": "geo:json", + "value": { + "type": "Point", + "coordinates": [float(data["Latitude"]), float(data["Longitude"])] + } + } + }) + + if updateResponse["Response"] == "OK": + _id = self.MongoDB.insertData(self.MongoDB.mongoConn.Life, { + "Use": "Application", + "Location": location, + "Zone": zone, + "Application": "NA", + "Device": device, + "Data": data, + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }, None) + Thread(target=self.Blockchain.storeHash, args=(str(_id), self.Blockchain.hashLifeData(data), int(time.time()), + locationID, device, bcAddress, "Device"), daemon=True).start() + self.Helpers.logger.info("Device " + device + " status update OK") + else: + self.Helpers.logger.error("Device " + device + " life update KO") + + def deviceCommandsCallback(self, topic, payload): + """ + iotJumpWay Device Commands Callback + + The callback function that is triggerend in the event of an device + command communication from the iotJumpWay. + """ + + self.Helpers.logger.info( + "Recieved iotJumpWay Device Command Data: " + payload.decode()) + + command = json.loads(payload.decode("utf-8")) + splitTopic = topic.split("/") + + location = splitTopic[0] + zone = splitTopic[2] + device = splitTopic[3] + + requiredAttributes = self.ContextBroker.getRequiredAttributes( + device, "Device") + + locationID = int(requiredAttributes["Data"]["lid"]["value"]) + bcAddress = requiredAttributes["Data"]["blockchain"]["address"] + + if not self.Blockchain.iotJumpWayAccessCheck(bcAddress): + return + + _id = self.MongoDB.insertData(self.MongoDB.mongoConn.Commands, { + "Use": "Device", + "Location": location, + "Zone": zone, + "From": command["From"], + "To": device, + "Type": command["Type"], + "Value": command["Value"], + "Message": command["Message"], + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }, None) + + Thread(target=self.Blockchain.storeHash, args=(str(_id), self.Blockchain.hashCommand(command), int(time.time()), + locationID, device, bcAddress, "Device"), daemon=True).start() + self.Helpers.logger.info("Device " + device + " command update OK") + + def deviceNfcCallback(self, topic, payload): + """ + iotJumpWay Device NFC Callback + + The callback function that is triggered in the event of a device + NFC communication from the iotJumpWay. + """ + + self.Helpers.logger.info( + "Recieved iotJumpWay Device NFC Data: " + payload.decode()) + + data = json.loads(payload.decode("utf-8")) + splitTopic = topic.split("/") + + location = splitTopic[0] + zone = splitTopic[2] + device = splitTopic[3] + + requiredAttributes = self.ContextBroker.getRequiredAttributes( + device, "Device") + + locationID = int(requiredAttributes["Data"]["lid"]["value"]) + bcAddress = requiredAttributes["Data"]["blockchain"]["address"] + + if not self.Blockchain.iotJumpWayAccessCheck(bcAddress): + return + + check = self.ContextBroker.getNFC(data["Value"]) + if check["Response"] is "OK" and len(check["Data"]): + self.Application.appDeviceChannelPub("Commands", zone, device, { + "From": str(self.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["aid"]), + "Type": "NFC", + "Value": "Not Authorized", + "Message": "NFC Chip Not Authorized" + }) + self.Helpers.logger.info("Device " + device + " NFC Not Allowed KO") + return + + _id = self.MongoDB.insertData(self.MongoDB.mongoConn.NFC, { + "Use": "Device", + "Location": location, + "Zone": zone, + "Application": 0, + "Device": device, + "Sensor": data["Sensor"], + "Value": data["Value"], + "Message": data["Message"], + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }, None) + + self.Application.appDeviceChannelPub("Commands", zone, device, { + "From": str(self.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["aid"]), + "Type": "NFC", + "Value": "Authorized", + "Message": "NFC Chip Authorized" + }) + + Thread(target=self.Blockchain.storeHash, args=(str(_id), self.Blockchain.hashNfc(data), int(time.time()), + locationID, device, bcAddress, "Device"), daemon=True).start() + self.Helpers.logger.info("Device " + device + " NFC Allowed OK") + + def deviceSensorCallback(self, topic, payload): + """ + iotJumpWay Application Sensors Callback + + The callback function that is triggered in the event of an device + sensor communication from the iotJumpWay. + """ + + self.Helpers.logger.info( + "Recieved iotJumpWay Device Sensors Data : " + payload.decode()) + + data = json.loads(payload.decode("utf-8")) + splitTopic = topic.split("/") + + location = splitTopic[0] + zone = splitTopic[2] + device = splitTopic[3] + + requiredAttributes = self.ContextBroker.getRequiredAttributes( + device, "Device") + + locationID = int(requiredAttributes["Data"]["lid"]["value"]) + bcAddress = requiredAttributes["Data"]["blockchain"]["address"] + + if not self.Blockchain.iotJumpWayAccessCheck(bcAddress): + return + + _id = self.MongoDB.insertData(self.MongoDB.mongoConn.Sensors, { + "Use": "Device", + "Location": location, + "Zone": zone, + "Application": 0, + "Device": device, + "Sensor": data["Sensor"], + "Type": data["Type"], + "Value": data["Value"], + "Message": data["Message"], + "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }, None) + + Thread(target=self.Blockchain.storeHash, args=(str(_id), self.Blockchain.hashSensorData(data), int(time.time()), + locationID, device, bcAddress, "Device"), daemon=True).start() + self.Helpers.logger.info("Device " + device + " NFC Allowed OK") + + def respond(self, responseCode, response): + """ Builds the request repsonse """ + + return Response(response=json.dumps(response, indent=4), status=responseCode, + mimetype="application/json") + + def signal_handler(self, signal, frame): + self.Helpers.logger.info("Disconnecting") + self.Application.appDisconnect() + sys.exit(1) + +app = Flask(__name__) +MQTT = MQTT() + +@app.route('/About', methods=['GET']) +def about(): + """ Responds to POST requests sent to the North Port About API endpoint. """ + + return MQTT.respond(200, { + "Identifier": MQTT.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["identifier"], + "Host": MQTT.Helpers.confs["iotJumpWay"]["ip"], + "NorthPort": MQTT.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["northPort"], + "CPU": psutil.cpu_percent(), + "Memory": psutil.virtual_memory()[2], + "Diskspace": psutil.disk_usage('/').percent, + "Temperature": psutil.sensors_temperatures()['coretemp'][0].current + }) + +@app.route('/Commands', methods=['POST']) +def commands(): + """ Responds to POST requests sent to the North Port Commands API endpoint. """ + + if request.headers["Content-Type"] == "application/json": + command = request.json + if command["ToType"] is "Device": + MQTT.Application.appDeviceChannelPub("Commands", command["ToLocation"], command["ToDevice"], { + "From": command["From"], + "Type": command["FromType"], + "Value": command["Value"], + "Message": command["Message"] + }) + elif command["ToType"] is "Application": + MQTT.Application.appChannelPub("Commands", command["ToApplication"], { + "From": command["From"], + "Type": command["FromType"], + "Value": command["Value"], + "Message": command["Message"] + }) + else: + return MQTT.respond(400, { + "Response": "Failed", + "Error": "BadRequest", + "Description": "Command type not supported!" + }) + else: + return MQTT.respond(405, { + "Response": "Failed", + "Error": "MethodNotAlowed", + "Description": "Method not allowed!" + }) + +def main(): + + signal.signal(signal.SIGINT, MQTT.signal_handler) + signal.signal(signal.SIGTERM, MQTT.signal_handler) + + # Starts the application + MQTT.contextConn() + MQTT.mongoDbConn() + MQTT.blockchainConn() + MQTT.startIoT() + + Thread(target=MQTT.life, args=(), daemon=True).start() + + app.run(host=MQTT.Helpers.confs["iotJumpWay"]["ip"], + port=MQTT.Helpers.confs["iotJumpWay"]["MQTT"]["Agent"]["northPort"]) + +if __name__ == "__main__": + main() From 91510a51895c9c9177aa375b2cdb65195bb752af Mon Sep 17 00:00:00 2001 From: AdamMiltonBarker Date: Thu, 22 Oct 2020 01:16:49 +0200 Subject: [PATCH 07/22] 2.0.0 Services --- Services/CamAPI.py | 39 ++- Services/Client.py | 2 +- Services/Replenish.py | 11 +- Services/Test/.keep | 0 Services/iotJumpWay.py | 534 ----------------------------------------- 5 files changed, 36 insertions(+), 550 deletions(-) create mode 100644 Services/Test/.keep delete mode 100644 Services/iotJumpWay.py diff --git a/Services/CamAPI.py b/Services/CamAPI.py index 85255e3..25e969d 100644 --- a/Services/CamAPI.py +++ b/Services/CamAPI.py @@ -11,7 +11,7 @@ # allowing devices on the HIAS network to identify known and unknown users via # HTTP request. # License: MIT License -# Last Modified: 2020-09-23 +# Last Modified: 2020-10-18 # ###################################################################################################### @@ -25,13 +25,16 @@ import sys import threading +sys.path.insert(0, os.path.abspath( + os.path.join(os.path.dirname(__file__), '..'))) + import numpy as np from flask import Flask, request, Response from threading import Thread from Classes.Helpers import Helpers -from Classes.iotJumpWay import Device as iot +from Classes.MQTT import Device from Classes.TassAI import TassAI from Classes.OpenVINO.ie_module import InferenceContext @@ -77,7 +80,7 @@ def life(self): hdd = psutil.disk_usage('/').percent tmp = psutil.sensors_temperatures()['coretemp'][0].current r = requests.get('http://ipinfo.io/json?token=' + - self.Helpers.confs["iotJumpWay"]["ipinfo"]) + self.Helpers.confs["iotJumpWay"]["ipinfo"]) data = r.json() location = data["loc"].split(',') @@ -100,6 +103,25 @@ def life(self): threading.Timer(300.0, self.life).start() + def startIoT(self): + """ Initiates the iotJumpWay connection. """ + + self.Device = Device({ + "host": self.Helpers.confs["iotJumpWay"]["host"], + "port": self.Helpers.confs["iotJumpWay"]["MQTT"]["port"], + "lid": self.Helpers.confs["iotJumpWay"]["MQTT"]["TassAI"]["lid"], + "zid": self.Helpers.confs["iotJumpWay"]["MQTT"]["TassAI"]["zid"], + "did": self.Helpers.confs["iotJumpWay"]["MQTT"]["TassAI"]["did"], + "an": self.Helpers.confs["iotJumpWay"]["MQTT"]["TassAI"]["dn"], + "un": self.Helpers.confs["iotJumpWay"]["MQTT"]["TassAI"]["un"], + "pw": self.Helpers.confs["iotJumpWay"]["MQTT"]["TassAI"]["pw"] + }) + self.Device.connect() + self.Device.channelSub() + self.Device.commandsCallback = self.commandsCallback + + self.Helpers.logger.info("iotJumpWay connection initiated.") + def threading(self): """ Creates required module threads. """ @@ -115,11 +137,6 @@ def signal_handler(self, signal, frame): app = Flask(__name__) CamAPI = CamAPI() -@app.route('/Encode', methods=['POST']) -def Encode(): - """ Responds to POST requests sent to the /Encode API endpoint. """ - - @app.route('/Inference', methods=['POST']) def Inference(): """ Responds to POST requests sent to the /Inference API endpoint. """ @@ -192,10 +209,12 @@ def main(): # Starts threading signal.signal(signal.SIGINT, CamAPI.signal_handler) signal.signal(signal.SIGTERM, CamAPI.signal_handler) + + CamAPI.startIoT() CamAPI.threading() - app.run(host=CamAPI.Helpers.confs["TassAI"]["ip"], - port=CamAPI.Helpers.confs["TassAI"]["port"]) + app.run(host=CamAPI.Helpers.confs["iotJumpWay"]["ip"], + port=CamAPI.Helpers.confs["iotJumpWay"]["MQTT"]["TassAI"]["port"]) if __name__ == "__main__": main() diff --git a/Services/Client.py b/Services/Client.py index 388464e..9995784 100644 --- a/Services/Client.py +++ b/Services/Client.py @@ -44,7 +44,7 @@ def send(self, imagePath): _, img_encoded = cv2.imencode('.jpg', img) response = requests.post(self.addr, data=img_encoded.tostring(), - headers=self.headers) + headers=self.headers) response = json.loads(response.text) diff --git a/Services/Replenish.py b/Services/Replenish.py index 7b903b8..8c5f4d9 100644 --- a/Services/Replenish.py +++ b/Services/Replenish.py @@ -10,15 +10,19 @@ # Description: The replenish module is used to replenish HIAS Smart Contracts with Ether so that they # can function. # License: MIT License -# Last Modified: 2020-09-27 +# Last Modified: 2020-10-18 # ###################################################################################################### import time +import os +import sys + +sys.path.insert(0, os.path.abspath( + os.path.join(os.path.dirname(__file__), '..'))) from Classes.Helpers import Helpers from Classes.Blockchain import Blockchain -from Classes.MySQL import MySQL class Replenish(): """ Replenish Class @@ -32,9 +36,6 @@ def __init__(self): self.Helpers = Helpers("Replenish") - self.MySQL = MySQL() - self.MySQL.startMySQL() - self.Blockchain = Blockchain() self.Blockchain.startBlockchain() self.Blockchain.w3.geth.personal.unlockAccount( diff --git a/Services/Test/.keep b/Services/Test/.keep new file mode 100644 index 0000000..e69de29 diff --git a/Services/iotJumpWay.py b/Services/iotJumpWay.py deleted file mode 100644 index 895d421..0000000 --- a/Services/iotJumpWay.py +++ /dev/null @@ -1,534 +0,0 @@ -#!/usr/bin/env python3 -###################################################################################################### -# -# Organization: Peter Moss Leukemia AI Research -# Repository: HIAS: Hospital Intelligent Automation System -# -# Author: Adam Milton-Barker (AdamMiltonBarker.com) -# -# Title: iotJumpWay Class -# Description: The iotJumpWay module is used by the HIAS iotJumpWay service to collect -# and store all data from the iotJumpWay devices on the HIAS network. -# License: MIT License -# Last Modified: 2020-09-27 -# -###################################################################################################### - -import base64 -import json -import logging -import psutil -import requests -import signal -import sys -import time -import threading -import hmac - -from threading import Thread - -from datetime import datetime -from datetime import timedelta - -from requests.auth import HTTPBasicAuth -from web3 import Web3 - -from Classes.Helpers import Helpers -from Classes.Blockchain import Blockchain -from Classes.iotJumpWay import Application -from Classes.MongoDB import MongoDB -from Classes.MySQL import MySQL - -class iotJumpWay(): - """ iotJumpWay Class - - The iotJumpWay Class listens for data from the network and - stores it in the Mongo db. - """ - - def __init__(self): - """ Initializes the class. """ - - self.Helpers = Helpers("GeniSysAI") - self.Helpers.logger.info("GeniSysAI Class initialization complete.") - - def startIoT(self): - """ Initiates the iotJumpWay connection class. """ - - self.Application = Application({ - "host": self.Helpers.confs["iotJumpWay"]["host"], - "port": self.Helpers.confs["iotJumpWay"]["port"], - "lid": self.Helpers.confs["iotJumpWay"]["lid"], - "aid": self.Helpers.confs["iotJumpWay"]["paid"], - "an": self.Helpers.confs["iotJumpWay"]["pan"], - "un": self.Helpers.confs["iotJumpWay"]["pun"], - "pw": self.Helpers.confs["iotJumpWay"]["ppw"] - }) - self.Application.connect() - - self.Application.appChannelSub("#","#") - self.Application.appDeviceChannelSub("#", "#", "#") - - self.Application.appCommandsCallback = self.appCommandsCallback - self.Application.appLifeCallback = self.appLifeCallback - self.Application.appSensorCallback = self.appSensorCallback - self.Application.appStatusCallback = self.appStatusCallback - self.Application.appTriggerCallback = self.appTriggerCallback - self.Application.deviceCameraCallback = self.deviceCameraCallback - self.Application.deviceCommandsCallback = self.deviceCommandsCallback - self.Application.deviceNfcCallback = self.deviceNfcCallback - self.Application.deviceSensorCallback = self.deviceSensorCallback - self.Application.deviceStatusCallback = self.deviceStatusCallback - self.Application.deviceTriggerCallback = self.deviceTriggerCallback - self.Application.deviceLifeCallback = self.deviceLifeCallback - - self.Helpers.logger.info("GeniSysAI Class initialization complete.") - - def mySqlConn(self): - """ Initiates the MySQL connection class. """ - - self.MySQL = MySQL() - self.MySQL.startMySQL() - - def mongoDbConn(self): - """ Initiates the MongoDB connection class. """ - - self.MongoDB = MongoDB() - self.MongoDB.startMongoDB() - - def blockchainConn(self): - """ Initiates the Blockchain connection class. """ - - self.Blockchain = Blockchain() - self.Blockchain.startBlockchain() - self.Blockchain.w3.geth.personal.unlockAccount( - self.Helpers.confs["ethereum"]["haddress"], self.Helpers.confs["ethereum"]["hpass"], 0) - self.Blockchain.w3.geth.personal.unlockAccount( - self.Helpers.confs["ethereum"]["iaddress"], self.Helpers.confs["ethereum"]["ipass"], 0) - - def life(self): - """ Sends vital statistics to HIAS """ - - cpu = psutil.cpu_percent() - mem = psutil.virtual_memory()[2] - hdd = psutil.disk_usage('/').percent - tmp = psutil.sensors_temperatures()['coretemp'][0].current - r = requests.get('http://ipinfo.io/json?token=' + - self.Helpers.confs["iotJumpWay"]["ipinfo"]) - data = r.json() - location = data["loc"].split(',') - - self.Helpers.logger.info("GeniSysAI Life (TEMPERATURE): " + str(tmp) + "\u00b0") - self.Helpers.logger.info("GeniSysAI Life (CPU): " + str(cpu) + "%") - self.Helpers.logger.info("GeniSysAI Life (Memory): " + str(mem) + "%") - self.Helpers.logger.info("GeniSysAI Life (HDD): " + str(hdd) + "%") - self.Helpers.logger.info("GeniSysAI Life (LAT): " + str(location[0])) - self.Helpers.logger.info("GeniSysAI Life (LNG): " + str(location[1])) - - # Send iotJumpWay notification - self.Application.appChannelPub("Life", self.Helpers.confs["iotJumpWay"]["paid"], { - "CPU": str(cpu), - "Memory": str(mem), - "Diskspace": str(hdd), - "Temperature": str(tmp), - "Latitude": float(location[0]), - "Longitude": float(location[1]) - }) - - threading.Timer(300.0, self.life).start() - - def appStatusCallback(self, topic, payload): - """ - iotJumpWay Application Status Callback - - The callback function that is triggerend in the event of an application - status communication from the iotJumpWay. - """ - - self.Helpers.logger.info("Recieved iotJumpWay Application Status: " + payload.decode()) - - splitTopic=topic.split("/") - status = payload.decode() - - application = self.MySQL.getApplication(splitTopic[2]) - - if not self.Blockchain.iotJumpWayAccessCheck(application[12]): - return - - self.MySQL.updateApplicationStatus(status, splitTopic) - - _id = self.MongoDB.insertData(self.MongoDB.mongoConn.Statuses, { - "Use": "Application", - "Location": splitTopic[0], - "Zone": 0, - "Application": splitTopic[2], - "Device": 0, - "Status": status, - "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - Thread(target=self.Blockchain.storeHash, args=(str(_id), self.Blockchain.hashStatus(status), int(time.time()), int(splitTopic[2]), - application[10], application[12], "App"), - daemon=True).start() - - def appLifeCallback(self, topic, payload): - """ - iotJumpWay Application Life Callback - - The callback function that is triggerend in the event of a application - life communication from the iotJumpWay. - """ - - self.Helpers.logger.info("Recieved iotJumpWay Application Life Data: " + payload.decode()) - - data = json.loads(payload.decode("utf-8")) - splitTopic = topic.split("/") - - application = self.MySQL.getApplication(splitTopic[2]) - - if not self.Blockchain.iotJumpWayAccessCheck(application[12]): - return - - self.MySQL.updateApplication("Life", data, splitTopic) - - _id = self.MongoDB.insertData(self.MongoDB.mongoConn.Life, { - "Use": "Application", - "Location": splitTopic[0], - "Zone": 0, - "Application": splitTopic[2], - "Device": 0, - "Data": data, - "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - Thread(target=self.Blockchain.storeHash, args=(str(_id), self.Blockchain.hashLifeData(data), int(time.time()), int(splitTopic[2]), - application[10], application[12], "App"), - daemon=True).start() - - def appCommandsCallback(self, topic, payload): - """ - iotJumpWay Application Commands Callback - - The callback function that is triggerend in the event of an application - command communication from the iotJumpWay. - """ - - self.Helpers.logger.info("Recieved iotJumpWay Application Command Data: " + payload.decode()) - - command = json.loads(payload.decode("utf-8")) - splitTopic=topic.split("/") - - self.MongoDB.insertData(self.MongoDB.mongoConn.Commands, { - "Use": "Application", - "Location": splitTopic[0], - "Zone": 0, - "From": command["From"], - "To": splitTopic[3], - "Type": command["Type"], - "Value": command["Value"], - "Message": command["Message"], - "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - def appSensorCallback(self, topic, payload): - """ - iotJumpWay Application Sensors Callback - - The callback function that is triggerend in the event of an application - sensor communication from the iotJumpWay. - """ - - self.Helpers.logger.info("Recieved iotJumpWay Application Sensors Data: " + payload.decode()) - command = json.loads(payload.decode("utf-8")) - - splitTopic = topic.split("/") - - self.MongoDB.insertData(self.MongoDB.mongoConn.Sensors, { - "Use": "Application", - "Location": splitTopic[0], - "Zone": 0, - "Application": splitTopic[2], - "Device": 0, - "Sensor": command["Sensor"], - "Type": command["Type"], - "Value": command["Value"], - "Message": command["Message"], - "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - def appTriggerCallback(self, topic, payload): - """ - iotJumpWay Application Trigger Callback - - The callback function that is triggerend in the event of an application - trigger communication from the iotJumpWay. - """ - - self.Helpers.logger.info("Recieved iotJumpWay Application Trigger Data: " + payload.decode()) - command = json.loads(payload.decode("utf-8")) - - def deviceStatusCallback(self, topic, payload): - """ - iotJumpWay Device Status Callback - - The callback function that is triggerend in the event of an device - status communication from the iotJumpWay. - """ - - self.Helpers.logger.info("Recieved iotJumpWay Device Status Data: " + payload.decode()) - - splitTopic = topic.split("/") - status = payload.decode() - - device = self.MySQL.getDevice(splitTopic[3]) - - if not self.Blockchain.iotJumpWayAccessCheck(device[8]): - return - - self.MySQL.updateDeviceStatus(status, splitTopic[3]) - - _id = self.MongoDB.insertData(self.MongoDB.mongoConn.Statuses, { - "Use": "Device", - "Location": splitTopic[0], - "Zone": splitTopic[2], - "Application": 0, - "Device": splitTopic[3], - "Status": status, - "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - Thread(target=self.Blockchain.storeHash, args=(str(_id), self.Blockchain.hashStatus(status), int(time.time()), int(splitTopic[3]), - device[10], device[8], "Device"), - daemon=True).start() - - def deviceLifeCallback(self, topic, payload): - """ - iotJumpWay Device Life Callback - - The callback function that is triggerend in the event of a device - life communication from the iotJumpWay. - """ - - self.Helpers.logger.info("Recieved iotJumpWay Device Life Data : " + payload.decode()) - - data = json.loads(payload.decode("utf-8")) - splitTopic = topic.split("/") - - device = self.MySQL.getDevice(splitTopic[3]) - - if not self.Blockchain.iotJumpWayAccessCheck(device[8]): - return - - self.MySQL.updateDevice("Life", data, splitTopic[3]) - - _id = self.MongoDB.insertData(self.MongoDB.mongoConn.Life, { - "Use": "Device", - "Location": splitTopic[0], - "Zone": splitTopic[2], - "Application": 0, - "Device": splitTopic[3], - "Data": data, - "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - Thread(target=self.Blockchain.storeHash, args=(str(_id), self.Blockchain.hashLifeData(data), int(time.time()), int(splitTopic[3]), - device[10], device[8], "Device"), - daemon=True).start() - - def deviceCommandsCallback(self, topic, payload): - """ - iotJumpWay Application Commands Callback - - The callback function that is triggerend in the event of an device - command communication from the iotJumpWay. - """ - - self.Helpers.logger.info("Recieved iotJumpWay Device Command Data: " + payload.decode()) - - command = json.loads(payload.decode("utf-8")) - splitTopic = topic.split("/") - - device = self.MySQL.getDevice(splitTopic[3]) - - if not self.Blockchain.iotJumpWayAccessCheck(device[8]): - return - - _id = self.MongoDB.insertData(self.MongoDB.mongoConn.Commands, { - "Use": "Device", - "Location": splitTopic[0], - "Zone": splitTopic[2], - "From": command["From"], - "To": splitTopic[3], - "Type": command["Type"], - "Value": command["Value"], - "Message": command["Message"], - "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - Thread(target=self.Blockchain.storeHash, args=(str(_id), self.Blockchain.hashCommand(command), int(time.time()), int(splitTopic[3]), - device[10], device[8], "Device"), - daemon=True).start() - - def deviceNfcCallback(self, topic, payload): - """ - iotJumpWay Device NFC Callback - - The callback function that is triggerend in the event of a device - NFC communication from the iotJumpWay. - """ - - self.Helpers.logger.info("Recieved iotJumpWay Device NFC Data: " + payload.decode()) - data = json.loads(payload.decode("utf-8")) - splitTopic = topic.split("/") - - if not self.MySQL.getUserNFC(data["Value"]): - # Send iotJumpWay command - self.Application.appDeviceChannelPub("Commands", splitTopic[2], splitTopic[3], { - "From": str(self.Helpers.confs["iotJumpWay"]["paid"]), - "Type": "NFC", - "Value": "Not Authorized", - "Message": "NFC Chip Not Authorized" - }) - return - - device = self.MySQL.getDevice(splitTopic[3]) - - if not self.Blockchain.iotJumpWayAccessCheck(device[8]): - return - - _id = self.MongoDB.insertData(self.MongoDB.mongoConn.NFC, { - "Use": "Device", - "Location": splitTopic[0], - "Zone": splitTopic[2], - "Application": 0, - "Device": splitTopic[3], - "Sensor": data["Sensor"], - "Value": data["Value"], - "Message": data["Message"], - "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - self.Application.appDeviceChannelPub("Commands", splitTopic[2], splitTopic[3], { - "From": str(splitTopic[3]), - "Type": "NFC", - "Value": "Authorized", - "Message": "NFC Chip Authorized" - }) - - Thread(target=self.Blockchain.storeHash, args=(str(_id), self.Blockchain.hashNfc(data), int(time.time()), int(splitTopic[3]), - device[10], device[8], "Device"), - daemon=True).start() - - def deviceSensorCallback(self, topic, payload): - """ - iotJumpWay Application Sensors Callback - - The callback function that is triggerend in the event of an device - sensor communication from the iotJumpWay. - """ - - self.Helpers.logger.info("Recieved iotJumpWay Device Sensors Data : " + payload.decode()) - data = json.loads(payload.decode("utf-8")) - splitTopic = topic.split("/") - - device = self.MySQL.getDevice(splitTopic[3]) - - if not self.Blockchain.iotJumpWayAccessCheck(device[8]): - return - - _id = self.MongoDB.insertData(self.MongoDB.mongoConn.Sensors, { - "Use": "Device", - "Location": splitTopic[0], - "Zone": splitTopic[2], - "Application": 0, - "Device": splitTopic[3], - "Sensor": data["Sensor"], - "Type": data["Type"], - "Value": data["Value"], - "Message": data["Message"], - "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - Thread(target=self.Blockchain.storeHash, args=(str(_id), self.Blockchain.hashSensorData(data), int(time.time()), int(splitTopic[3]), - device[10], device[8], "Device"), - daemon=True).start() - - def deviceTriggerCallback(self, topic, payload): - """ - iotJumpWay Application Trigger Callback - - The callback function that is triggerend in the event of an device - trigger communication from the iotJumpWay. - """ - - self.Helpers.logger.info("Recieved iotJumpWay Device Trigger Data: " + payload.decode()) - command = json.loads(payload.decode("utf-8")) - - def deviceCameraCallback(self, topic, payload): - """ - iotJumpWay Device Camera Callback - - The callback function that is trigge105rend in the event of a camera detecting a - known user or intruder. - """ - - self.Helpers.logger.info("Recieved iotJumpWay Device Camera Data: " + payload.decode()) - - data = json.loads(payload.decode("utf-8")) - splitTopic = topic.split("/") - - nlu = self.MySQL.getNLU(splitTopic) - - if nlu is not "": - - if data["Value"] is not 0: - - self.MySQL.updateUserLocation(splitTopic, data) - - self.MongoDB.insertData(self.MongoDB.mongoConn.Users, { - "User": int(data["Value"]), - "Location": splitTopic[0], - "Zone": splitTopic[2], - "Device": splitTopic[3], - "Time": datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - - userDetails = self.MySQL.getUser(data) - - if userDetails[1] and nlu[0]: - self.Application.appDeviceChannelPub("Commands", splitTopic[2], nlu[0], { - "From": str(splitTopic[3]), - "Type": "Welcome", - "Message": "Welcome " + userDetails[0], - "Value": userDetails[0]}) - self.MySQL.updateUser(data) - - elif userDetails[1] and userDetails[2] is "ONLINE": - print("SEND USER APP NOTIFICATION") - - def signal_handler(self, signal, frame): - self.Helpers.logger.info("Disconnecting") - self.Application.appDisconnect() - sys.exit(1) - -iotJumpWay = iotJumpWay() - -def main(): - - signal.signal(signal.SIGINT, iotJumpWay.signal_handler) - signal.signal(signal.SIGTERM, iotJumpWay.signal_handler) - - # Starts the application - iotJumpWay.mySqlConn() - iotJumpWay.mongoDbConn() - iotJumpWay.blockchainConn() - iotJumpWay.startIoT() - - Thread(target=iotJumpWay.life, args=(), daemon=True).start() - - while True: - time.sleep(1) - exit() - -if __name__ == "__main__": - main() From b530bb51281d2dd4fe922a0bb2183964a0cd5af2 Mon Sep 17 00:00:00 2001 From: AdamMiltonBarker Date: Thu, 22 Oct 2020 01:17:50 +0200 Subject: [PATCH 08/22] 2.0.0 Root --- Root/var/www/Classes/Core/GeniSys.php | 128 +- Root/var/www/Classes/Core/confs.json | 14 +- Root/var/www/Classes/Core/init.php | 82 +- Root/var/www/Classes/helpers.php | 187 +- .../ALL/CNN => AI/ALL}/Classes/ALL.js | 78 +- Root/var/www/html/AI/ALL/Classes/ALL.php | 1687 ++ Root/var/www/html/AI/ALL/Classify.php | 202 + Root/var/www/html/AI/ALL/Create.php | 422 + .../Data/__init__.py => AI/ALL/Data/.keep} | 0 Root/var/www/html/AI/ALL/Device.php | 955 + Root/var/www/html/AI/ALL/index.php | 178 + Root/var/www/html/AI/AML/Classes/AML.js | 176 + Root/var/www/html/AI/AML/Classes/AML.php | 1687 ++ Root/var/www/html/AI/AML/Classify.php | 200 + Root/var/www/html/AI/AML/Create.php | 422 + .../Data/__init__.py => AI/AML/Data/.keep} | 0 Root/var/www/html/AI/AML/Device.php | 955 + Root/var/www/html/AI/AML/index.php | 178 + Root/var/www/html/AI/COVID/Classes/COVID.js | 179 + Root/var/www/html/AI/COVID/Classes/COVID.php | 1687 ++ Root/var/www/html/AI/COVID/Classify.php | 202 + Root/var/www/html/AI/COVID/Create.php | 422 + .../Data/__init__.py => AI/COVID/Data/.keep} | 0 Root/var/www/html/AI/COVID/Device.php | 955 + Root/var/www/html/AI/COVID/index.php | 179 + Root/var/www/html/AI/Classes/AI.js | 128 + Root/var/www/html/AI/Classes/AI.php | 704 + Root/var/www/html/AI/Create.php | 299 + .../html/{ => AI}/GeniSysAI/Classes/NLU.js | 76 +- .../var/www/html/AI/GeniSysAI/Classes/NLU.php | 1673 ++ Root/var/www/html/AI/GeniSysAI/Create.php | 385 + Root/var/www/html/AI/GeniSysAI/Device.php | 913 + .../html/AI/GeniSysAI/Media/CSS/GeniSys.css | 25 + .../{ => AI}/GeniSysAI/Media/JS/GeniSysAi.js | 1 + .../GeniSysAI}/Media/JS/Logging.js | 0 .../GeniSysAI}/Media/JS/Validation.js | 0 Root/var/www/html/AI/GeniSysAI/index.php | 172 + Root/var/www/html/AI/Model.php | 382 + Root/var/www/html/AI/Skin/Classes/Skin.js | 176 + Root/var/www/html/AI/Skin/Classes/Skin.php | 1687 ++ Root/var/www/html/AI/Skin/Classify.php | 202 + Root/var/www/html/AI/Skin/Create.php | 422 + .../Live/index.php => AI/Skin/Data/.keep} | 0 Root/var/www/html/AI/Skin/Device.php | 955 + Root/var/www/html/AI/Skin/index.php | 178 + Root/var/www/html/AI/TassAI/Classes/TassAI.js | 56 + .../var/www/html/AI/TassAI/Classes/TassAI.php | 1527 ++ Root/var/www/html/AI/TassAI/Create.php | 397 + Root/var/www/html/AI/TassAI/Device.php | 938 + .../.htaccess => AI/TassAI/Live/index.php} | 0 Root/var/www/html/AI/TassAI/View.php | 123 + Root/var/www/html/AI/TassAI/index.php | 172 + Root/var/www/html/AI/index.php | 171 + Root/var/www/html/Blockchain/CCreate.php | 2 +- .../www/html/Blockchain/Classes/Blockchain.js | 3 +- .../html/Blockchain/Classes/Blockchain.php | 26 +- Root/var/www/html/Blockchain/Contract.php | 2 +- Root/var/www/html/Blockchain/Contracts.php | 30 +- .../www/html/Blockchain/Media/CSS/GeniSys.css | 9 - .../www/html/Blockchain/Media/JS/GeniSysAi.js | 155 - Root/var/www/html/Blockchain/Transaction.php | 2 +- Root/var/www/html/Blockchain/Transfer.php | 35 +- Root/var/www/html/Blockchain/index.php | 162 +- Root/var/www/html/Dashboard.php | 145 +- .../COVID-19/Classes/COVID19.php | 34 - .../html/Data-Analysis/COVID-19/Data/.keep | 0 .../www/html/Data-Analysis/COVID-19/Pulls.php | 2 +- .../www/html/Data-Analysis/COVID-19/index.php | 2 +- .../html/Detection/ALL/CNN/Classes/ALL.php | 210 - Root/var/www/html/Detection/ALL/CNN/index.php | 190 - .../Detection/COVID-19/CNN/Classes/COVID19.js | 118 - .../COVID-19/CNN/Classes/COVID19.php | 209 - .../www/html/Detection/COVID-19/CNN/index.php | 191 - Root/var/www/html/GeniSysAI/Classes/NLU.php | 769 - Root/var/www/html/GeniSysAI/Device.php | 683 - .../www/html/GeniSysAI/Media/CSS/GeniSys.css | 9 - .../www/html/GeniSysAI/Media/JS/Logging.js | 17 - .../www/html/GeniSysAI/Media/JS/Validation.js | 122 - Root/var/www/html/GeniSysAI/index.php | 159 - Root/var/www/html/Hospital/Beds/Bed.php | 449 - .../www/html/Hospital/Beds/Classes/Beds.js | 157 - .../www/html/Hospital/Beds/Classes/Beds.php | 784 - Root/var/www/html/Hospital/Beds/Create.php | 189 - Root/var/www/html/Hospital/Beds/index.php | 150 - .../Hospital/Patients/Classes/Patients.js | 55 +- .../Hospital/Patients/Classes/Patients.php | 1068 +- .../var/www/html/Hospital/Patients/Create.php | 354 +- .../Images/Uploads/Adam-Milton-Barker.png | Bin 470001 -> 0 bytes .../www/html/Hospital/Patients/Patient.php | 200 +- .../html/Hospital/Patients/Transaction.php | 20 - Root/var/www/html/Hospital/Patients/index.php | 37 +- .../API/Applications/NLU/Classes/Auth.php | 165 - .../Staff/API/Applications/NLU/index.php | 173 - .../API/Applications/User/Classes/Auth.php | 160 - .../Staff/API/Applications/User/index.php | 75 - .../Staff/API/Applications/User/pbkdf2.php | 114 - .../www/html/Hospital/Staff/Classes/Staff.js | 57 +- .../www/html/Hospital/Staff/Classes/Staff.php | 1803 +- Root/var/www/html/Hospital/Staff/Create.php | 83 +- Root/var/www/html/Hospital/Staff/Staff.php | 212 +- Root/var/www/html/Hospital/Staff/index.php | 268 +- Root/var/www/html/Includes/Footer.php | 4 +- Root/var/www/html/Includes/JS.php | 54 +- Root/var/www/html/Includes/LeftNav.php | 194 +- Root/var/www/html/Includes/Nav.php | 118 +- Root/var/www/html/Includes/RightNav.php | 100 +- Root/var/www/html/Includes/Stats.php | 170 +- Root/var/www/html/Includes/Time.php | 61 - Root/var/www/html/Includes/Version.php | 15 - Root/var/www/html/Media/CSS/sb-admin-2.css | 459 + .../var/www/html/Media/CSS/sb-admin-2.min.css | 5 + .../var/www/html/Media/Images/Site/burger.png | Bin 0 -> 19914 bytes .../www/html/Media/Images/Site/favicon.png | Bin 0 -> 309483 bytes Root/var/www/html/Media/Images/Site/home.jpg | Bin 0 -> 5885706 bytes .../var/www/html/Media/Images/Site/loader.gif | Bin 0 -> 55820 bytes .../html/Media/Images/profiles/__init__.py | 0 Root/var/www/html/Media/JS/sb-admin-2.js | 42 + Root/var/www/html/Media/JS/sb-admin-2.min.js | 6 + Root/var/www/html/Media/data/flot-data.js | 1242 ++ Root/var/www/html/Media/data/morris-data.js | 117 + Root/var/www/html/Media/less/mixins.less | 1 + Root/var/www/html/Media/less/sb-admin-2.less | 548 + Root/var/www/html/Media/less/variables.less | 14 + .../bootstrap-social/bootstrap-social.css | 101 + .../bootstrap-social/bootstrap-social.less | 114 + .../bootstrap-social/bootstrap-social.scss | 114 + .../Media/vendor/bootstrap/css/bootstrap.css | 6765 +++++++ .../vendor/bootstrap/css/bootstrap.min.css | 6 + .../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes .../fonts/glyphicons-halflings-regular.svg | 288 + .../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes .../Media/vendor/bootstrap/js/bootstrap.js | 2377 +++ .../vendor/bootstrap/js/bootstrap.min.js | 7 + .../dataTables.bootstrap.css | 314 + .../dataTables.bootstrap.js | 186 + .../dataTables.bootstrap.min.js | 8 + .../vendor/datatables-plugins/index.html | 442 + .../dataTables.responsive.css | 106 + .../dataTables.responsive.js | 873 + .../dataTables.responsive.scss | 149 + .../datatables/css/dataTables.bootstrap.css | 185 + .../css/dataTables.bootstrap.min.css | 1 + .../datatables/css/dataTables.bootstrap4.css | 193 + .../css/dataTables.bootstrap4.min.css | 1 + .../datatables/css/dataTables.foundation.css | 116 + .../css/dataTables.foundation.min.css | 1 + .../datatables/css/dataTables.jqueryui.css | 481 + .../css/dataTables.jqueryui.min.css | 1 + .../datatables/css/dataTables.material.css | 87 + .../css/dataTables.material.min.css | 1 + .../datatables/css/dataTables.semanticui.css | 103 + .../css/dataTables.semanticui.min.css | 1 + .../datatables/css/dataTables.uikit.css | 146 + .../datatables/css/dataTables.uikit.min.css | 1 + .../datatables/css/jquery.dataTables.css | 452 + .../datatables/css/jquery.dataTables.min.css | 1 + .../css/jquery.dataTables_themeroller.css | 416 + .../datatables/images/Sorting icons.psd | Bin 0 -> 27490 bytes .../vendor/datatables/images/favicon.ico | Bin 0 -> 894 bytes .../vendor/datatables/images/sort_asc.png | Bin 0 -> 160 bytes .../datatables/images/sort_asc_disabled.png | Bin 0 -> 148 bytes .../vendor/datatables/images/sort_both.png | Bin 0 -> 201 bytes .../vendor/datatables/images/sort_desc.png | Bin 0 -> 158 bytes .../datatables/images/sort_desc_disabled.png | Bin 0 -> 146 bytes .../datatables/js/dataTables.bootstrap.js | 182 + .../datatables/js/dataTables.bootstrap.min.js | 8 + .../datatables/js/dataTables.bootstrap4.js | 184 + .../js/dataTables.bootstrap4.min.js | 8 + .../datatables/js/dataTables.foundation.js | 174 + .../js/dataTables.foundation.min.js | 8 + .../datatables/js/dataTables.jqueryui.js | 164 + .../datatables/js/dataTables.jqueryui.min.js | 9 + .../datatables/js/dataTables.material.js | 191 + .../datatables/js/dataTables.material.min.js | 8 + .../datatables/js/dataTables.semanticui.js | 208 + .../js/dataTables.semanticui.min.js | 9 + .../vendor/datatables/js/dataTables.uikit.js | 176 + .../datatables/js/dataTables.uikit.min.js | 8 + .../vendor/datatables/js/jquery.dataTables.js | 15278 ++++++++++++++++ .../datatables/js/jquery.dataTables.min.js | 166 + .../html/Media/vendor/datatables/js/jquery.js | 5 + .../flot-tooltip/jquery.flot.tooltip.js | 604 + .../flot-tooltip/jquery.flot.tooltip.min.js | 12 + .../jquery.flot.tooltip.source.js | 593 + .../www/html/Media/vendor/flot/excanvas.js | 1428 ++ .../html/Media/vendor/flot/excanvas.min.js | 1 + .../Media/vendor/flot/jquery.colorhelpers.js | 180 + .../Media/vendor/flot/jquery.flot.canvas.js | 345 + .../vendor/flot/jquery.flot.categories.js | 190 + .../vendor/flot/jquery.flot.crosshair.js | 176 + .../vendor/flot/jquery.flot.errorbars.js | 353 + .../vendor/flot/jquery.flot.fillbetween.js | 226 + .../Media/vendor/flot/jquery.flot.image.js | 241 + .../www/html/Media/vendor/flot/jquery.flot.js | 3168 ++++ .../Media/vendor/flot/jquery.flot.navigate.js | 346 + .../html/Media/vendor/flot/jquery.flot.pie.js | 820 + .../Media/vendor/flot/jquery.flot.resize.js | 59 + .../vendor/flot/jquery.flot.selection.js | 360 + .../Media/vendor/flot/jquery.flot.stack.js | 188 + .../Media/vendor/flot/jquery.flot.symbol.js | 71 + .../vendor/flot/jquery.flot.threshold.js | 142 + .../Media/vendor/flot/jquery.flot.time.js | 432 + Root/var/www/html/Media/vendor/flot/jquery.js | 9472 ++++++++++ .../Media/vendor/font-awesome/HELP-US-OUT.txt | 7 + .../vendor/font-awesome/css/font-awesome.css | 2199 +++ .../font-awesome/css/font-awesome.css.map | 7 + .../font-awesome/css/font-awesome.min.css | 4 + .../vendor/font-awesome/fonts/FontAwesome.otf | Bin 0 -> 124988 bytes .../fonts/fontawesome-webfont.eot | Bin 0 -> 76518 bytes .../fonts/fontawesome-webfont.svg | 685 + .../fonts/fontawesome-webfont.ttf | Bin 0 -> 152796 bytes .../fonts/fontawesome-webfont.woff | Bin 0 -> 90412 bytes .../fonts/fontawesome-webfont.woff2 | Bin 0 -> 71896 bytes .../vendor/font-awesome/less/animated.less | 34 + .../font-awesome/less/bordered-pulled.less | 25 + .../Media/vendor/font-awesome/less/core.less | 12 + .../vendor/font-awesome/less/extras.less | 2 + .../vendor/font-awesome/less/fixed-width.less | 6 + .../font-awesome/less/font-awesome.less | 18 + .../Media/vendor/font-awesome/less/icons.less | 733 + .../vendor/font-awesome/less/larger.less | 13 + .../Media/vendor/font-awesome/less/list.less | 19 + .../vendor/font-awesome/less/mixins.less | 60 + .../Media/vendor/font-awesome/less/path.less | 15 + .../font-awesome/less/rotated-flipped.less | 20 + .../font-awesome/less/screen-reader.less | 5 + .../vendor/font-awesome/less/spinning.less | 29 + .../vendor/font-awesome/less/stacked.less | 20 + .../vendor/font-awesome/less/variables.less | 744 + .../vendor/font-awesome/scss/_animated.scss | 34 + .../font-awesome/scss/_bordered-pulled.scss | 25 + .../Media/vendor/font-awesome/scss/_core.scss | 12 + .../vendor/font-awesome/scss/_extras.scss | 44 + .../font-awesome/scss/_fixed-width.scss | 6 + .../vendor/font-awesome/scss/_icons.scss | 733 + .../vendor/font-awesome/scss/_larger.scss | 13 + .../Media/vendor/font-awesome/scss/_list.scss | 19 + .../vendor/font-awesome/scss/_mixins.scss | 60 + .../Media/vendor/font-awesome/scss/_path.scss | 15 + .../font-awesome/scss/_rotated-flipped.scss | 20 + .../font-awesome/scss/_screen-reader.scss | 5 + .../vendor/font-awesome/scss/_spinning.scss | 29 + .../vendor/font-awesome/scss/_stacked.scss | 20 + .../vendor/font-awesome/scss/_variables.scss | 744 + .../font-awesome/scss/font-awesome.scss | 18 + .../www/html/Media/vendor/jquery/jquery.js | 10074 ++++++++++ .../html/Media/vendor/jquery/jquery.min.js | 4 + .../html/Media/vendor/metisMenu/metisMenu.css | 64 + .../html/Media/vendor/metisMenu/metisMenu.js | 120 + .../Media/vendor/metisMenu/metisMenu.min.css | 10 + .../Media/vendor/metisMenu/metisMenu.min.js | 9 + .../www/html/Media/vendor/morrisjs/morris.css | 2 + .../www/html/Media/vendor/morrisjs/morris.js | 1892 ++ .../html/Media/vendor/morrisjs/morris.min.js | 7 + .../www/html/Media/vendor/raphael/raphael.js | 8293 +++++++++ .../html/Media/vendor/raphael/raphael.min.js | 3 + Root/var/www/html/Password.php | 26 +- .../www/html/Robotics/EMAR/Classes/EMAR.js | 114 +- .../www/html/Robotics/EMAR/Classes/EMAR.php | 1682 +- Root/var/www/html/Robotics/EMAR/Create.php | 336 +- Root/var/www/html/Robotics/EMAR/Device.php | 441 +- Root/var/www/html/Robotics/EMAR/index.php | 41 +- .../Security/GeniSysAI/Classes/GeniSysAI.js | 183 - .../Security/GeniSysAI/Classes/GeniSysAI.php | 870 - .../www/html/Security/GeniSysAI/Create.php | 221 - .../www/html/Security/GeniSysAI/Device.php | 699 - .../var/www/html/Security/GeniSysAI/index.php | 189 - Root/var/www/html/Server/Settings.php | 34 +- Root/var/www/html/index.php | 5 +- .../www/html/iotJumpWay/AMQP/API/Resource.php | 109 + .../www/html/iotJumpWay/AMQP/API/Topic.php | 114 + .../var/www/html/iotJumpWay/AMQP/API/User.php | 112 + .../www/html/iotJumpWay/AMQP/API/Vhost.php | 97 + Root/var/www/html/iotJumpWay/Application.php | 379 +- .../iotJumpWay/ApplicationTransaction.php | 17 +- Root/var/www/html/iotJumpWay/Applications.php | 23 +- .../www/html/iotJumpWay/Classes/iotJumpWay.js | 8 +- .../html/iotJumpWay/Classes/iotJumpWay.php | 3752 +++- .../html/iotJumpWay/Classes/iotJumpWayUI.js | 287 +- .../ContextBroker.php} | 116 +- .../html/iotJumpWay/ContextBroker/Agent.php | 863 + .../iotJumpWay/ContextBroker/AgentCreate.php | 298 + .../html/iotJumpWay/ContextBroker/Agents.php | 192 + .../ContextBroker/Classes/ContextBroker.js | 266 + .../ContextBroker/Classes/ContextBroker.php | 2004 ++ .../iotJumpWay/ContextBroker/Settings.php | 237 + .../ContextBroker}/Transaction.php | 35 +- Root/var/www/html/iotJumpWay/CreateApp.php | 360 +- Root/var/www/html/iotJumpWay/CreateDevice.php | 209 +- .../{Sensor.php => CreateThing.php} | 65 +- Root/var/www/html/iotJumpWay/CreateZone.php | 188 +- Root/var/www/html/iotJumpWay/Data.php | 57 +- Root/var/www/html/iotJumpWay/Device.php | 367 +- .../www/html/iotJumpWay/DeviceTransaction.php | 15 +- Root/var/www/html/iotJumpWay/Devices.php | 23 +- .../html/iotJumpWay/Includes/iotJumpWay.php | 2 +- .../iotJumpWay/Media/Images/Things/.htaccess | 0 .../{Sensors => Things}/LCD-KeyPad-4.jpg | Bin .../{Sensors => Things}/LCD-KeyPad-4.png | Bin .../Images/{Sensors => Things}/NFCScanner.jpg | Bin .../Images/{Sensors => Things}/NFCScanner.png | Bin .../Images/{Sensors => Things}/RainSensor.jpg | Bin .../Images/{Sensors => Things}/RainSensor.png | Bin .../{Sensors => Things}/WaterLevelSensor.jpg | Bin .../{Sensors => Things}/WaterLevelSensor.png | Bin .../Images/{Sensors => Things}/button.jpg | Bin .../Images/{Sensors => Things}/button.png | Bin .../Images/{Sensors => Things}/buzzer.jpg | Bin .../Images/{Sensors => Things}/buzzer.png | Bin .../Media/Images/{Sensors => Things}/cctv.jpg | Bin .../Media/Images/{Sensors => Things}/cctv.png | Bin .../Media/Images/{Sensors => Things}/led.jpg | Bin .../Media/Images/{Sensors => Things}/led.png | Bin .../{Sensors => Things}/light-sensor.jpg | Bin .../{Sensors => Things}/light-sensor.png | Bin .../{Sensors => Things}/moisture-sensor.jpg | Bin .../{Sensors => Things}/moisture-sensor.png | Bin .../{Sensors => Things}/motion-sensor.jpg | Bin .../{Sensors => Things}/motion-sensor.png | Bin .../{Sensors => Things}/reed-switch.jpg | Bin .../{Sensors => Things}/reed-switch.png | Bin .../Images/{Sensors => Things}/relay.jpg | Bin .../Images/{Sensors => Things}/relay.png | Bin .../Images/{Sensors => Things}/servo.jpg | Bin .../Images/{Sensors => Things}/servo.png | Bin .../{Sensors => Things}/servoController.jpg | Bin .../{Sensors => Things}/servoController.png | Bin .../{Sensors => Things}/sound-sensor.jpg | Bin .../{Sensors => Things}/sound-sensor.png | Bin .../{Sensors => Things}/temperature.jpg | Bin .../{Sensors => Things}/temperature.png | Bin .../virtual-controller.png | Bin Root/var/www/html/iotJumpWay/Thing.php | 267 + .../{SensorUpload.php => ThingUpdate.php} | 16 +- .../{SensorUpdate.php => ThingUpload.php} | 14 +- .../iotJumpWay/{Sensors.php => Things.php} | 26 +- Root/var/www/html/iotJumpWay/Zone.php | 223 +- Root/var/www/html/iotJumpWay/Zones.php | 120 +- Root/var/www/html/iotJumpWay/index.php | 345 +- .../bootstrap/dist/css/bootstrap.css | 2 +- 342 files changed, 117208 insertions(+), 11949 deletions(-) rename Root/var/www/html/{Detection/ALL/CNN => AI/ALL}/Classes/ALL.js (52%) create mode 100644 Root/var/www/html/AI/ALL/Classes/ALL.php create mode 100644 Root/var/www/html/AI/ALL/Classify.php create mode 100644 Root/var/www/html/AI/ALL/Create.php rename Root/var/www/html/{Data-Analysis/COVID-19/Data/__init__.py => AI/ALL/Data/.keep} (100%) create mode 100644 Root/var/www/html/AI/ALL/Device.php create mode 100644 Root/var/www/html/AI/ALL/index.php create mode 100644 Root/var/www/html/AI/AML/Classes/AML.js create mode 100644 Root/var/www/html/AI/AML/Classes/AML.php create mode 100644 Root/var/www/html/AI/AML/Classify.php create mode 100644 Root/var/www/html/AI/AML/Create.php rename Root/var/www/html/{Detection/ALL/CNN/Data/__init__.py => AI/AML/Data/.keep} (100%) create mode 100644 Root/var/www/html/AI/AML/Device.php create mode 100644 Root/var/www/html/AI/AML/index.php create mode 100644 Root/var/www/html/AI/COVID/Classes/COVID.js create mode 100644 Root/var/www/html/AI/COVID/Classes/COVID.php create mode 100644 Root/var/www/html/AI/COVID/Classify.php create mode 100644 Root/var/www/html/AI/COVID/Create.php rename Root/var/www/html/{Detection/COVID-19/CNN/Data/__init__.py => AI/COVID/Data/.keep} (100%) create mode 100644 Root/var/www/html/AI/COVID/Device.php create mode 100644 Root/var/www/html/AI/COVID/index.php create mode 100644 Root/var/www/html/AI/Classes/AI.js create mode 100644 Root/var/www/html/AI/Classes/AI.php create mode 100644 Root/var/www/html/AI/Create.php rename Root/var/www/html/{ => AI}/GeniSysAI/Classes/NLU.js (63%) create mode 100644 Root/var/www/html/AI/GeniSysAI/Classes/NLU.php create mode 100644 Root/var/www/html/AI/GeniSysAI/Create.php create mode 100644 Root/var/www/html/AI/GeniSysAI/Device.php create mode 100644 Root/var/www/html/AI/GeniSysAI/Media/CSS/GeniSys.css rename Root/var/www/html/{ => AI}/GeniSysAI/Media/JS/GeniSysAi.js (99%) rename Root/var/www/html/{Blockchain => AI/GeniSysAI}/Media/JS/Logging.js (100%) rename Root/var/www/html/{Blockchain => AI/GeniSysAI}/Media/JS/Validation.js (100%) create mode 100644 Root/var/www/html/AI/GeniSysAI/index.php create mode 100644 Root/var/www/html/AI/Model.php create mode 100644 Root/var/www/html/AI/Skin/Classes/Skin.js create mode 100644 Root/var/www/html/AI/Skin/Classes/Skin.php create mode 100644 Root/var/www/html/AI/Skin/Classify.php create mode 100644 Root/var/www/html/AI/Skin/Create.php rename Root/var/www/html/{Security/GeniSysAI/Live/index.php => AI/Skin/Data/.keep} (100%) create mode 100644 Root/var/www/html/AI/Skin/Device.php create mode 100644 Root/var/www/html/AI/Skin/index.php create mode 100644 Root/var/www/html/AI/TassAI/Classes/TassAI.js create mode 100644 Root/var/www/html/AI/TassAI/Classes/TassAI.php create mode 100644 Root/var/www/html/AI/TassAI/Create.php create mode 100644 Root/var/www/html/AI/TassAI/Device.php rename Root/var/www/html/{iotJumpWay/Media/Images/Sensors/.htaccess => AI/TassAI/Live/index.php} (100%) create mode 100644 Root/var/www/html/AI/TassAI/View.php create mode 100644 Root/var/www/html/AI/TassAI/index.php create mode 100644 Root/var/www/html/AI/index.php delete mode 100644 Root/var/www/html/Blockchain/Media/CSS/GeniSys.css delete mode 100644 Root/var/www/html/Blockchain/Media/JS/GeniSysAi.js rename Test/__init__.py => Root/var/www/html/Data-Analysis/COVID-19/Data/.keep (100%) delete mode 100644 Root/var/www/html/Detection/ALL/CNN/Classes/ALL.php delete mode 100644 Root/var/www/html/Detection/ALL/CNN/index.php delete mode 100644 Root/var/www/html/Detection/COVID-19/CNN/Classes/COVID19.js delete mode 100644 Root/var/www/html/Detection/COVID-19/CNN/Classes/COVID19.php delete mode 100644 Root/var/www/html/Detection/COVID-19/CNN/index.php delete mode 100644 Root/var/www/html/GeniSysAI/Classes/NLU.php delete mode 100644 Root/var/www/html/GeniSysAI/Device.php delete mode 100644 Root/var/www/html/GeniSysAI/Media/CSS/GeniSys.css delete mode 100644 Root/var/www/html/GeniSysAI/Media/JS/Logging.js delete mode 100644 Root/var/www/html/GeniSysAI/Media/JS/Validation.js delete mode 100644 Root/var/www/html/GeniSysAI/index.php delete mode 100644 Root/var/www/html/Hospital/Beds/Bed.php delete mode 100644 Root/var/www/html/Hospital/Beds/Classes/Beds.js delete mode 100644 Root/var/www/html/Hospital/Beds/Classes/Beds.php delete mode 100644 Root/var/www/html/Hospital/Beds/Create.php delete mode 100644 Root/var/www/html/Hospital/Beds/index.php delete mode 100644 Root/var/www/html/Hospital/Patients/Media/Images/Uploads/Adam-Milton-Barker.png delete mode 100644 Root/var/www/html/Hospital/Staff/API/Applications/NLU/Classes/Auth.php delete mode 100644 Root/var/www/html/Hospital/Staff/API/Applications/NLU/index.php delete mode 100644 Root/var/www/html/Hospital/Staff/API/Applications/User/Classes/Auth.php delete mode 100644 Root/var/www/html/Hospital/Staff/API/Applications/User/index.php delete mode 100644 Root/var/www/html/Hospital/Staff/API/Applications/User/pbkdf2.php delete mode 100644 Root/var/www/html/Includes/Time.php delete mode 100644 Root/var/www/html/Includes/Version.php create mode 100644 Root/var/www/html/Media/CSS/sb-admin-2.css create mode 100644 Root/var/www/html/Media/CSS/sb-admin-2.min.css create mode 100644 Root/var/www/html/Media/Images/Site/burger.png create mode 100644 Root/var/www/html/Media/Images/Site/favicon.png create mode 100644 Root/var/www/html/Media/Images/Site/home.jpg create mode 100644 Root/var/www/html/Media/Images/Site/loader.gif create mode 100644 Root/var/www/html/Media/Images/profiles/__init__.py create mode 100644 Root/var/www/html/Media/JS/sb-admin-2.js create mode 100644 Root/var/www/html/Media/JS/sb-admin-2.min.js create mode 100644 Root/var/www/html/Media/data/flot-data.js create mode 100644 Root/var/www/html/Media/data/morris-data.js create mode 100644 Root/var/www/html/Media/less/mixins.less create mode 100644 Root/var/www/html/Media/less/sb-admin-2.less create mode 100644 Root/var/www/html/Media/less/variables.less create mode 100644 Root/var/www/html/Media/vendor/bootstrap-social/bootstrap-social.css create mode 100644 Root/var/www/html/Media/vendor/bootstrap-social/bootstrap-social.less create mode 100644 Root/var/www/html/Media/vendor/bootstrap-social/bootstrap-social.scss create mode 100644 Root/var/www/html/Media/vendor/bootstrap/css/bootstrap.css create mode 100644 Root/var/www/html/Media/vendor/bootstrap/css/bootstrap.min.css create mode 100644 Root/var/www/html/Media/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot create mode 100644 Root/var/www/html/Media/vendor/bootstrap/fonts/glyphicons-halflings-regular.svg create mode 100644 Root/var/www/html/Media/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf create mode 100644 Root/var/www/html/Media/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff create mode 100644 Root/var/www/html/Media/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff2 create mode 100644 Root/var/www/html/Media/vendor/bootstrap/js/bootstrap.js create mode 100644 Root/var/www/html/Media/vendor/bootstrap/js/bootstrap.min.js create mode 100644 Root/var/www/html/Media/vendor/datatables-plugins/dataTables.bootstrap.css create mode 100644 Root/var/www/html/Media/vendor/datatables-plugins/dataTables.bootstrap.js create mode 100644 Root/var/www/html/Media/vendor/datatables-plugins/dataTables.bootstrap.min.js create mode 100644 Root/var/www/html/Media/vendor/datatables-plugins/index.html create mode 100644 Root/var/www/html/Media/vendor/datatables-responsive/dataTables.responsive.css create mode 100644 Root/var/www/html/Media/vendor/datatables-responsive/dataTables.responsive.js create mode 100644 Root/var/www/html/Media/vendor/datatables-responsive/dataTables.responsive.scss create mode 100644 Root/var/www/html/Media/vendor/datatables/css/dataTables.bootstrap.css create mode 100644 Root/var/www/html/Media/vendor/datatables/css/dataTables.bootstrap.min.css create mode 100644 Root/var/www/html/Media/vendor/datatables/css/dataTables.bootstrap4.css create mode 100644 Root/var/www/html/Media/vendor/datatables/css/dataTables.bootstrap4.min.css create mode 100644 Root/var/www/html/Media/vendor/datatables/css/dataTables.foundation.css create mode 100644 Root/var/www/html/Media/vendor/datatables/css/dataTables.foundation.min.css create mode 100644 Root/var/www/html/Media/vendor/datatables/css/dataTables.jqueryui.css create mode 100644 Root/var/www/html/Media/vendor/datatables/css/dataTables.jqueryui.min.css create mode 100644 Root/var/www/html/Media/vendor/datatables/css/dataTables.material.css create mode 100644 Root/var/www/html/Media/vendor/datatables/css/dataTables.material.min.css create mode 100644 Root/var/www/html/Media/vendor/datatables/css/dataTables.semanticui.css create mode 100644 Root/var/www/html/Media/vendor/datatables/css/dataTables.semanticui.min.css create mode 100644 Root/var/www/html/Media/vendor/datatables/css/dataTables.uikit.css create mode 100644 Root/var/www/html/Media/vendor/datatables/css/dataTables.uikit.min.css create mode 100644 Root/var/www/html/Media/vendor/datatables/css/jquery.dataTables.css create mode 100644 Root/var/www/html/Media/vendor/datatables/css/jquery.dataTables.min.css create mode 100644 Root/var/www/html/Media/vendor/datatables/css/jquery.dataTables_themeroller.css create mode 100644 Root/var/www/html/Media/vendor/datatables/images/Sorting icons.psd create mode 100644 Root/var/www/html/Media/vendor/datatables/images/favicon.ico create mode 100644 Root/var/www/html/Media/vendor/datatables/images/sort_asc.png create mode 100644 Root/var/www/html/Media/vendor/datatables/images/sort_asc_disabled.png create mode 100644 Root/var/www/html/Media/vendor/datatables/images/sort_both.png create mode 100644 Root/var/www/html/Media/vendor/datatables/images/sort_desc.png create mode 100644 Root/var/www/html/Media/vendor/datatables/images/sort_desc_disabled.png create mode 100644 Root/var/www/html/Media/vendor/datatables/js/dataTables.bootstrap.js create mode 100644 Root/var/www/html/Media/vendor/datatables/js/dataTables.bootstrap.min.js create mode 100644 Root/var/www/html/Media/vendor/datatables/js/dataTables.bootstrap4.js create mode 100644 Root/var/www/html/Media/vendor/datatables/js/dataTables.bootstrap4.min.js create mode 100644 Root/var/www/html/Media/vendor/datatables/js/dataTables.foundation.js create mode 100644 Root/var/www/html/Media/vendor/datatables/js/dataTables.foundation.min.js create mode 100644 Root/var/www/html/Media/vendor/datatables/js/dataTables.jqueryui.js create mode 100644 Root/var/www/html/Media/vendor/datatables/js/dataTables.jqueryui.min.js create mode 100644 Root/var/www/html/Media/vendor/datatables/js/dataTables.material.js create mode 100644 Root/var/www/html/Media/vendor/datatables/js/dataTables.material.min.js create mode 100644 Root/var/www/html/Media/vendor/datatables/js/dataTables.semanticui.js create mode 100644 Root/var/www/html/Media/vendor/datatables/js/dataTables.semanticui.min.js create mode 100644 Root/var/www/html/Media/vendor/datatables/js/dataTables.uikit.js create mode 100644 Root/var/www/html/Media/vendor/datatables/js/dataTables.uikit.min.js create mode 100644 Root/var/www/html/Media/vendor/datatables/js/jquery.dataTables.js create mode 100644 Root/var/www/html/Media/vendor/datatables/js/jquery.dataTables.min.js create mode 100644 Root/var/www/html/Media/vendor/datatables/js/jquery.js create mode 100644 Root/var/www/html/Media/vendor/flot-tooltip/jquery.flot.tooltip.js create mode 100644 Root/var/www/html/Media/vendor/flot-tooltip/jquery.flot.tooltip.min.js create mode 100644 Root/var/www/html/Media/vendor/flot-tooltip/jquery.flot.tooltip.source.js create mode 100644 Root/var/www/html/Media/vendor/flot/excanvas.js create mode 100644 Root/var/www/html/Media/vendor/flot/excanvas.min.js create mode 100644 Root/var/www/html/Media/vendor/flot/jquery.colorhelpers.js create mode 100644 Root/var/www/html/Media/vendor/flot/jquery.flot.canvas.js create mode 100644 Root/var/www/html/Media/vendor/flot/jquery.flot.categories.js create mode 100644 Root/var/www/html/Media/vendor/flot/jquery.flot.crosshair.js create mode 100644 Root/var/www/html/Media/vendor/flot/jquery.flot.errorbars.js create mode 100644 Root/var/www/html/Media/vendor/flot/jquery.flot.fillbetween.js create mode 100644 Root/var/www/html/Media/vendor/flot/jquery.flot.image.js create mode 100644 Root/var/www/html/Media/vendor/flot/jquery.flot.js create mode 100644 Root/var/www/html/Media/vendor/flot/jquery.flot.navigate.js create mode 100644 Root/var/www/html/Media/vendor/flot/jquery.flot.pie.js create mode 100644 Root/var/www/html/Media/vendor/flot/jquery.flot.resize.js create mode 100644 Root/var/www/html/Media/vendor/flot/jquery.flot.selection.js create mode 100644 Root/var/www/html/Media/vendor/flot/jquery.flot.stack.js create mode 100644 Root/var/www/html/Media/vendor/flot/jquery.flot.symbol.js create mode 100644 Root/var/www/html/Media/vendor/flot/jquery.flot.threshold.js create mode 100644 Root/var/www/html/Media/vendor/flot/jquery.flot.time.js create mode 100644 Root/var/www/html/Media/vendor/flot/jquery.js create mode 100644 Root/var/www/html/Media/vendor/font-awesome/HELP-US-OUT.txt create mode 100644 Root/var/www/html/Media/vendor/font-awesome/css/font-awesome.css create mode 100644 Root/var/www/html/Media/vendor/font-awesome/css/font-awesome.css.map create mode 100644 Root/var/www/html/Media/vendor/font-awesome/css/font-awesome.min.css create mode 100644 Root/var/www/html/Media/vendor/font-awesome/fonts/FontAwesome.otf create mode 100644 Root/var/www/html/Media/vendor/font-awesome/fonts/fontawesome-webfont.eot create mode 100644 Root/var/www/html/Media/vendor/font-awesome/fonts/fontawesome-webfont.svg create mode 100644 Root/var/www/html/Media/vendor/font-awesome/fonts/fontawesome-webfont.ttf create mode 100644 Root/var/www/html/Media/vendor/font-awesome/fonts/fontawesome-webfont.woff create mode 100644 Root/var/www/html/Media/vendor/font-awesome/fonts/fontawesome-webfont.woff2 create mode 100644 Root/var/www/html/Media/vendor/font-awesome/less/animated.less create mode 100644 Root/var/www/html/Media/vendor/font-awesome/less/bordered-pulled.less create mode 100644 Root/var/www/html/Media/vendor/font-awesome/less/core.less create mode 100644 Root/var/www/html/Media/vendor/font-awesome/less/extras.less create mode 100644 Root/var/www/html/Media/vendor/font-awesome/less/fixed-width.less create mode 100644 Root/var/www/html/Media/vendor/font-awesome/less/font-awesome.less create mode 100644 Root/var/www/html/Media/vendor/font-awesome/less/icons.less create mode 100644 Root/var/www/html/Media/vendor/font-awesome/less/larger.less create mode 100644 Root/var/www/html/Media/vendor/font-awesome/less/list.less create mode 100644 Root/var/www/html/Media/vendor/font-awesome/less/mixins.less create mode 100644 Root/var/www/html/Media/vendor/font-awesome/less/path.less create mode 100644 Root/var/www/html/Media/vendor/font-awesome/less/rotated-flipped.less create mode 100644 Root/var/www/html/Media/vendor/font-awesome/less/screen-reader.less create mode 100644 Root/var/www/html/Media/vendor/font-awesome/less/spinning.less create mode 100644 Root/var/www/html/Media/vendor/font-awesome/less/stacked.less create mode 100644 Root/var/www/html/Media/vendor/font-awesome/less/variables.less create mode 100644 Root/var/www/html/Media/vendor/font-awesome/scss/_animated.scss create mode 100644 Root/var/www/html/Media/vendor/font-awesome/scss/_bordered-pulled.scss create mode 100644 Root/var/www/html/Media/vendor/font-awesome/scss/_core.scss create mode 100644 Root/var/www/html/Media/vendor/font-awesome/scss/_extras.scss create mode 100644 Root/var/www/html/Media/vendor/font-awesome/scss/_fixed-width.scss create mode 100644 Root/var/www/html/Media/vendor/font-awesome/scss/_icons.scss create mode 100644 Root/var/www/html/Media/vendor/font-awesome/scss/_larger.scss create mode 100644 Root/var/www/html/Media/vendor/font-awesome/scss/_list.scss create mode 100644 Root/var/www/html/Media/vendor/font-awesome/scss/_mixins.scss create mode 100644 Root/var/www/html/Media/vendor/font-awesome/scss/_path.scss create mode 100644 Root/var/www/html/Media/vendor/font-awesome/scss/_rotated-flipped.scss create mode 100644 Root/var/www/html/Media/vendor/font-awesome/scss/_screen-reader.scss create mode 100644 Root/var/www/html/Media/vendor/font-awesome/scss/_spinning.scss create mode 100644 Root/var/www/html/Media/vendor/font-awesome/scss/_stacked.scss create mode 100644 Root/var/www/html/Media/vendor/font-awesome/scss/_variables.scss create mode 100644 Root/var/www/html/Media/vendor/font-awesome/scss/font-awesome.scss create mode 100644 Root/var/www/html/Media/vendor/jquery/jquery.js create mode 100644 Root/var/www/html/Media/vendor/jquery/jquery.min.js create mode 100644 Root/var/www/html/Media/vendor/metisMenu/metisMenu.css create mode 100644 Root/var/www/html/Media/vendor/metisMenu/metisMenu.js create mode 100644 Root/var/www/html/Media/vendor/metisMenu/metisMenu.min.css create mode 100644 Root/var/www/html/Media/vendor/metisMenu/metisMenu.min.js create mode 100644 Root/var/www/html/Media/vendor/morrisjs/morris.css create mode 100644 Root/var/www/html/Media/vendor/morrisjs/morris.js create mode 100644 Root/var/www/html/Media/vendor/morrisjs/morris.min.js create mode 100644 Root/var/www/html/Media/vendor/raphael/raphael.js create mode 100644 Root/var/www/html/Media/vendor/raphael/raphael.min.js delete mode 100644 Root/var/www/html/Security/GeniSysAI/Classes/GeniSysAI.js delete mode 100644 Root/var/www/html/Security/GeniSysAI/Classes/GeniSysAI.php delete mode 100644 Root/var/www/html/Security/GeniSysAI/Create.php delete mode 100644 Root/var/www/html/Security/GeniSysAI/Device.php delete mode 100644 Root/var/www/html/Security/GeniSysAI/index.php create mode 100644 Root/var/www/html/iotJumpWay/AMQP/API/Resource.php create mode 100644 Root/var/www/html/iotJumpWay/AMQP/API/Topic.php create mode 100644 Root/var/www/html/iotJumpWay/AMQP/API/User.php create mode 100644 Root/var/www/html/iotJumpWay/AMQP/API/Vhost.php rename Root/var/www/html/{GeniSysAI/Create.php => iotJumpWay/ContextBroker.php} (51%) create mode 100644 Root/var/www/html/iotJumpWay/ContextBroker/Agent.php create mode 100644 Root/var/www/html/iotJumpWay/ContextBroker/AgentCreate.php create mode 100644 Root/var/www/html/iotJumpWay/ContextBroker/Agents.php create mode 100644 Root/var/www/html/iotJumpWay/ContextBroker/Classes/ContextBroker.js create mode 100644 Root/var/www/html/iotJumpWay/ContextBroker/Classes/ContextBroker.php create mode 100644 Root/var/www/html/iotJumpWay/ContextBroker/Settings.php rename Root/var/www/html/{Hospital/Beds => iotJumpWay/ContextBroker}/Transaction.php (76%) rename Root/var/www/html/iotJumpWay/{Sensor.php => CreateThing.php} (64%) create mode 100644 Root/var/www/html/iotJumpWay/Media/Images/Things/.htaccess rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/LCD-KeyPad-4.jpg (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/LCD-KeyPad-4.png (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/NFCScanner.jpg (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/NFCScanner.png (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/RainSensor.jpg (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/RainSensor.png (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/WaterLevelSensor.jpg (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/WaterLevelSensor.png (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/button.jpg (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/button.png (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/buzzer.jpg (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/buzzer.png (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/cctv.jpg (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/cctv.png (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/led.jpg (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/led.png (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/light-sensor.jpg (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/light-sensor.png (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/moisture-sensor.jpg (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/moisture-sensor.png (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/motion-sensor.jpg (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/motion-sensor.png (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/reed-switch.jpg (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/reed-switch.png (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/relay.jpg (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/relay.png (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/servo.jpg (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/servo.png (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/servoController.jpg (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/servoController.png (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/sound-sensor.jpg (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/sound-sensor.png (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/temperature.jpg (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/temperature.png (100%) rename Root/var/www/html/iotJumpWay/Media/Images/{Sensors => Things}/virtual-controller.png (100%) create mode 100644 Root/var/www/html/iotJumpWay/Thing.php rename Root/var/www/html/iotJumpWay/{SensorUpload.php => ThingUpdate.php} (64%) rename Root/var/www/html/iotJumpWay/{SensorUpdate.php => ThingUpload.php} (69%) rename Root/var/www/html/iotJumpWay/{Sensors.php => Things.php} (83%) diff --git a/Root/var/www/Classes/Core/GeniSys.php b/Root/var/www/Classes/Core/GeniSys.php index 13ea3f1..83fbb5b 100644 --- a/Root/var/www/Classes/Core/GeniSys.php +++ b/Root/var/www/Classes/Core/GeniSys.php @@ -20,6 +20,68 @@ function __construct($_GeniSys) $this->web3 = $this->blockchainConnection(); $this->contract = new Contract($this->web3->provider, $this->bcc["abi"]); endif; + $this->cb = $this->getContextBrokerConf(); + } + + public function getContextBrokerConf() + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM contextbroker + "); + $pdoQuery->execute(); + $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + return $response; + } + + private function createContextHeaders($username = "", $password = "") + { + if($username): + $basicAuth = $username . ":" . $password; + else: + $basicAuth = $_SESSION["GeniSysAI"]["User"] . ":" . $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"]); + endif; + $basicAuth = base64_encode($basicAuth); + + return [ + "Content-Type: application/json", + 'Authorization: Basic '. $basicAuth + ]; + } + + private function contextBrokerRequest($method, $url, $headers, $json) + { + $path = $this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/" . $this->cb["url"] . "/" . $url; + + if($method == "GET"): + $ch = curl_init(); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_URL, $path); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "POST"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + endif; + + return $body; } public function getBlockchainConf() @@ -138,14 +200,14 @@ public function login() endif; endif; - $gsysuser = $this->getUserByName(filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING)); + $gsysuser = $this->getUserByName(filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING), filter_input(INPUT_POST,'password',FILTER_SANITIZE_STRING)); if($gsysuser["id"]): if($this->verifyPassword(filter_input(INPUT_POST,'password',FILTER_SANITIZE_STRING), $this->_GeniSys->_helpers->oDecrypt($gsysuser["password"]))): session_regenerate_id(); $_SESSION["GeniSysAI"]=[ - "Active"=>true, + "Active"=>True, "Uid"=>$gsysuser["id"], "Identifier"=>$gsysuser["apub"], "User"=>filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING), @@ -153,7 +215,7 @@ public function login() "Pic"=>$gsysuser["pic"], "Mqtt"=> [ "Location" => $gsysuser["lid"], - "Application" => $gsysuser["aid"], + "Application" => $gsysuser["apub"], "ApplicationName" => $gsysuser["name"], "User" => $gsysuser["mqttu"], "Pass" => $gsysuser["mqttp"] @@ -405,49 +467,60 @@ public function getUser($userId) $this->checkBlockchainPermissions(); $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT id, - name, - password - FROM users - WHERE id = :id + SELECT users.id, + users.password, + mqtt.id as aid, + mqtt.apub + FROM users users + INNER JOIN mqtta mqtt + ON users.aid = mqtt.id + WHERE users.id = :id "); $pdoQuery->execute([ ":id"=> $userId ]); - $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $user=$pdoQuery->fetch(PDO::FETCH_ASSOC); $pdoQuery->closeCursor(); $pdoQuery = null; + $context = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $user["apub"] . "?attrs=name&type=Staff", $this->createContextHeaders(), []), true); + $user["name"] = $context["Data"]["name"]["value"]; - return $response; + return $user; } - public function getUserByName($username) + public function getUserByName($username = "", $password = "") { $pdoQuery = $this->_GeniSys->_secCon->prepare(" SELECT users.id, users.bcaddress, users.bcpw, users.password, - users.pic, - mqtt.lid, mqtt.id as aid, - mqtt.name, - mqtt.mqttu, - mqtt.apub, - mqtt.mqttp + mqtt.apub FROM users users INNER JOIN mqtta mqtt - ON users.id = mqtt.uid + ON users.aid = mqtt.id WHERE users.username = :username "); $pdoQuery->execute([ ":username"=> $username ]); - $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $user=$pdoQuery->fetch(PDO::FETCH_ASSOC); $pdoQuery->closeCursor(); $pdoQuery = null; - return $response; + if($password): + $context = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $user["apub"] . "?type=Staff", $this->createContextHeaders($username, $password), []), true); + else: + $context = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $user["apub"] . "?type=Staff", $this->createContextHeaders(), []), true); + endif; + $user["lid"] = $context["Data"]["lid"]["entity"]; + $user["pic"] = $context["Data"]["picture"]["value"]; + $user["name"] = $context["Data"]["name"]["value"]; + $user["mqttu"] = $context["Data"]["mqtt"]["username"]; + $user["mqttp"] = $context["Data"]["mqtt"]["password"]; + + return $user; } private static function verifyPassword($password,$hash) { @@ -456,20 +529,23 @@ private static function verifyPassword($password,$hash) { public function getStats() { - $this->checkBlockchainPermissions(); $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT cpu, - mem, - hdd, - tempr + SELECT apub FROM mqtta Where id = :id "); $pdoQuery->execute([ ":id" => $this->_GeniSys->_confs["aid"] ]); - $stats=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $coreApp=$pdoQuery->fetch(PDO::FETCH_ASSOC); + + $context = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $coreApp["apub"] . "?type=Application&attrs=batteryLevel.value,cpuUsage.value,memoryUsage.value,hddUsage.value,temperature.value", $this->createContextHeaders(), []), true); + + $stats["cpu"] = $context["Data"]["cpuUsage"]["value"]; + $stats["mem"] = $context["Data"]["memoryUsage"]["value"]; + $stats["hdd"] = $context["Data"]["hddUsage"]["value"]; + $stats["tempr"] = $context["Data"]["temperature"]["value"]; return $stats; } diff --git a/Root/var/www/Classes/Core/confs.json b/Root/var/www/Classes/Core/confs.json index 9104090..47a5861 100644 --- a/Root/var/www/Classes/Core/confs.json +++ b/Root/var/www/Classes/Core/confs.json @@ -1,9 +1,9 @@ { - "dbname": "", - "dbusername": "", - "dbpassword": "", - "mdbname": "", - "mdbusername": "", - "mdbpassword": "", - "key": "" + "dbname": "YourMysqlDatabaseName", + "dbusername": "YourMysqlDatabaseUsername", + "dbpassword": "YourMysqlDatabasePassword^", + "mdbname": "YourMongoDatabaseName", + "mdbusername": "YourMongoDatabaseUsername", + "mdbpassword": "YourMongoDatabasePassword", + "key": "YourEncryptionKey" } \ No newline at end of file diff --git a/Root/var/www/Classes/Core/init.php b/Root/var/www/Classes/Core/init.php index 42f1801..61c1b90 100644 --- a/Root/var/www/Classes/Core/init.php +++ b/Root/var/www/Classes/Core/init.php @@ -75,6 +75,7 @@ function __construct(Core $_secCon, $_pageDetails) include dirname(__FILE__) . '/../../Classes/helpers.php'; $this->_helpers = new Helpers($this); + $this->cb = $this->getContextBrokerConf(); $this->_confs = $this->getConfigs(); $this->_pageDetails = $_pageDetails; @@ -97,6 +98,63 @@ private function setCookie() endif; } + public function getContextBrokerConf() + { + $pdoQuery = $this->_secCon->prepare(" + SELECT * + FROM contextbroker + "); + $pdoQuery->execute(); + $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + return $response; + } + + private function createContextHeaders() + { + $basicAuth = $_SESSION["GeniSysAI"]["User"] . ":" . $this->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"]); + $basicAuth = base64_encode($basicAuth); + + return [ + "Content-Type: application/json", + 'Authorization: Basic '. $basicAuth + ]; + } + + private function contextBrokerRequest($method, $url, $headers, $json, $domain) + { + $path = $this->_helpers->oDecrypt($domain) . "/" . $this->cb["url"] . "/" . $url; + + if($method == "GET"): + $ch = curl_init(); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_URL, $path); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "POST"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + endif; + + return $body; + } + public function getBlockchainConf() { $pdoQuery = $this->_secCon->prepare(" @@ -175,22 +233,28 @@ protected function getConfigs() server.meta_title, server.meta_description, server.domainString, - mqtta.status, - mqtta.lt as alt, - mqtta.lg as alg, - mqtta.cpu, - mqtta.mem, - mqtta.hdd, - mqtta.tempr + mqtta.apub FROM settings server INNER JOIN mqtta mqtta ON mqtta.id = server.aid "); $pdoQuery->execute(); - $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $configs=$pdoQuery->fetch(PDO::FETCH_ASSOC); $pdoQuery->closeCursor(); $pdoQuery = null; - return $response; + + if(isSet($_SESSION["GeniSysAI"]["Active"])): + $context = json_decode($this->contextBrokerRequest("GET", "entities/" . $configs["apub"] . "?type=Application", $this->createContextHeaders(), [], $configs["domainString"]), true); + $configs["status"] = $context["Data"]["status"]["value"]; + $configs["cpu"] = $context["Data"]["cpuUsage"]["value"]; + $configs["mem"] = $context["Data"]["memoryUsage"]["value"]; + $configs["hdd"] = $context["Data"]["hddUsage"]["value"]; + $configs["tempr"] = $context["Data"]["temperature"]["value"]; + $configs["alt"] = $context["Data"]["location"]["value"]["coordinates"][0]; + $configs["alg"] = $context["Data"]["location"]["value"]["coordinates"][1]; + endif; + + return $configs; } public function updateConfigs() diff --git a/Root/var/www/Classes/helpers.php b/Root/var/www/Classes/helpers.php index ee8f6f4..2ae92a2 100644 --- a/Root/var/www/Classes/helpers.php +++ b/Root/var/www/Classes/helpers.php @@ -1,136 +1,71 @@ _GeniSys = $_GeniSys; - } + function __construct($_GeniSys) + { + $this->_GeniSys = $_GeniSys; + } - function decrypt($data) { - $encryption_key = base64_decode($this->_GeniSys->_key); - $method = "aes-" . strlen($encryption_key) * 8 . "-cbc"; - $iv = substr(base64_decode($data), 0, 16); - $decoded = openssl_decrypt(substr(base64_decode($data), 16), $method, $encryption_key, TRUE, $iv); - return $decoded; - } + function decrypt($data) { + $encryption_key = base64_decode($this->_GeniSys->_key); + $method = "aes-" . strlen($encryption_key) * 8 . "-cbc"; + $iv = substr(base64_decode($data), 0, 16); + $decoded = openssl_decrypt(substr(base64_decode($data), 16), $method, $encryption_key, TRUE, $iv); + return $decoded; + } - public function oDecrypt($encrypted) - { - $encryption_key = base64_decode($this->_GeniSys->_key); - list($encrypted_data, $iv) = explode("::", base64_decode($encrypted), 2); - return openssl_decrypt($encrypted_data, "aes-256-cbc", $encryption_key, 0, $iv); - } + public function oDecrypt($encrypted) + { + $encryption_key = base64_decode($this->_GeniSys->_key); + list($encrypted_data, $iv) = explode("::", base64_decode($encrypted), 2); + return openssl_decrypt($encrypted_data, "aes-256-cbc", $encryption_key, 0, $iv); + } - public function oEncrypt($value) - { - $encryption_key = base64_decode($this->_GeniSys->_key); - $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-cbc")); - $encrypted = openssl_encrypt($value, "aes-256-cbc", $encryption_key, 0, $iv); - return base64_encode($encrypted . "::" . $iv); - } + public function oEncrypt($value) + { + $encryption_key = base64_decode($this->_GeniSys->_key); + $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-cbc")); + $encrypted = openssl_encrypt($value, "aes-256-cbc", $encryption_key, 0, $iv); + return base64_encode($encrypted . "::" . $iv); + } - public function getUserIP() - { - if(array_key_exists("HTTP_X_FORWARDED_FOR", $_SERVER) && !empty($_SERVER["HTTP_X_FORWARDED_FOR"])): - if (strpos($_SERVER["HTTP_X_FORWARDED_FOR"],",") > 0): - $addr = explode( ",", $_SERVER["HTTP_X_FORWARDED_FOR"]); - return trim($addr[0]); - else: - return $_SERVER["HTTP_X_FORWARDED_FOR"]; - endif; - else: - return $_SERVER["REMOTE_ADDR"]; - endif; - } + public function getUserIP() + { + if(array_key_exists("HTTP_X_FORWARDED_FOR", $_SERVER) && !empty($_SERVER["HTTP_X_FORWARDED_FOR"])): + if (strpos($_SERVER["HTTP_X_FORWARDED_FOR"], ",") > 0): + $addr = explode(",", $_SERVER["HTTP_X_FORWARDED_FOR"]); + return trim($addr[0]); + else: + return $_SERVER["HTTP_X_FORWARDED_FOR"]; + endif; + else: + return $_SERVER["REMOTE_ADDR"]; + endif; + } - public function generate_uuid() { - return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', - mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), - mt_rand( 0, 0xffff ), - mt_rand( 0, 0x0C2f ) | 0x4000, - mt_rand( 0, 0x3fff ) | 0x8000, - mt_rand( 0, 0x2Aff ), mt_rand( 0, 0xffD3 ), mt_rand( 0, 0xff4B ) - ); - } + public static function verifyPassword($password, $hash) + { + return password_verify( + $password, + $hash); + } - public function password($l = 20, $c = 2, $n = 2, $s = 2) { - $out = ""; - $count = $c + $n + $s; - if(!is_int($l) || !is_int($c) || !is_int($n) || !is_int($s)) { - trigger_error('Argument(s) not an integer', E_USER_WARNING); - return false; - } - elseif($l < 0 || $l > 20 || $c < 0 || $n < 0 || $s < 0) { - trigger_error('Argument(s) out of range', E_USER_WARNING); - return false; - } - elseif($c > $l) { - trigger_error('Number of password capitals required exceeds password length', E_USER_WARNING); - return false; - } - elseif($n > $l) { - trigger_error('Number of password numerals exceeds password length', E_USER_WARNING); - return false; - } - elseif($s > $l) { - trigger_error('Number of password capitals exceeds password length', E_USER_WARNING); - return false; - } - elseif($count > $l) { - trigger_error('Number of password special characters exceeds specified password length', E_USER_WARNING); - return false; - } - $chars = "abcdefghijklmnopqrstuvwxyz"; - $caps = strtoupper($chars); - $nums = "0123456789"; - $syms = "!@#$%^&*()-_?"; - for($i = 0; $i < $l; $i++) { - $out .= substr($chars, mt_rand(0, strlen($chars) - 1), 1); - } - if($count) { - $tmp1 = str_split($out); - $tmp2 = array(); - for($i = 0; $i < $c; $i++) { - array_push($tmp2, substr($caps, mt_rand(0, strlen($caps) - 1), 1)); - } - for($i = 0; $i < $n; $i++) { - array_push($tmp2, substr($nums, mt_rand(0, strlen($nums) - 1), 1)); - } - for($i = 0; $i < $s; $i++) { - array_push($tmp2, substr($syms, mt_rand(0, strlen($syms) - 1), 1)); - } - $tmp1 = array_slice($tmp1, 0, $l - $count); - $tmp1 = array_merge($tmp1, $tmp2); - shuffle($tmp1); - $out = implode('', $tmp1); - } + public static function createPasswordHash($password) + { + return password_hash( + $password, + PASSWORD_DEFAULT); + } - return $out; - } - - public static function verifyPassword($password, $hash) - { - return password_verify( - $password, - $hash); - } - - public static function createPasswordHash($password) - { - return password_hash( - $password, - PASSWORD_DEFAULT); - } - - public function generateKey($length = 10){ - $characters="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321".time(); - $charactersLength = strlen($characters); - $randomString = ""; - for ($i = $length; $i > 0; $i--) - { - $randomString .= $characters[rand(0, $charactersLength - 1)]; - } - return $randomString; - } - } \ No newline at end of file + public function generateKey($length = 10){ + $characters="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321".time(); + $charactersLength = strlen($characters); + $randomString = ""; + for ($i = $length; $i > 0; $i--) + { + $randomString .= $characters[rand(0, $charactersLength - 1)]; + } + return $randomString; + } + } \ No newline at end of file diff --git a/Root/var/www/html/Detection/ALL/CNN/Classes/ALL.js b/Root/var/www/html/AI/ALL/Classes/ALL.js similarity index 52% rename from Root/var/www/html/Detection/ALL/CNN/Classes/ALL.js rename to Root/var/www/html/AI/ALL/Classes/ALL.js index 3ac2230..6065742 100644 --- a/Root/var/www/html/Detection/ALL/CNN/Classes/ALL.js +++ b/Root/var/www/html/AI/ALL/Classes/ALL.js @@ -1,12 +1,55 @@ var ALL = { + Create: function() { + $.post(window.location.href, $("#all_classifier").serialize(), function(resp) { + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + GeniSys.ResetForm("all_classifier"); + $('.modal-title').text('ALL Classifier Devices'); + $('.modal-body').html("HIAS ALL Classifier Device ID #" + resp.GDID + " created! Please save the API keys safely. The device's credentials are provided below. The credentials can be reset in the GeniSyAI Security Devices area.

Device ID: " + resp.DID + "
MQTT User: " + resp.MU + "
MQTT Password: " + resp.MP + "

Blockchain User: " + resp.BU + "
Blockchain Pass: " + resp.BP + "

App ID: " + resp.AppID + "
App Key: " + resp.AppKey + "

" + resp.Message); + $('#responsive-modal').modal('show'); + Logging.logMessage("Core", "Forms", "Device ID #" + resp.DID + " created!"); + break; + default: + msg = "ALL Create Failed: " + resp.Message + Logging.logMessage("Core", "ALL", msg); + $('.modal-title').text('ALL Classifier Devices'); + $('.modal-body').text(msg); + $('#responsive-modal').modal('show'); + break; + } + }); + }, + Update: function() { + $.post(window.location.href, $("#all_classifier_update").serialize(), function(resp) { + console.log(resp) + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + var fjson = JSON.stringify(resp.Schema, null, '\t'); + window.parent.$('#schema').html(fjson); + Logging.logMessage("Core", "Forms", "Device Update OK"); + $('.modal-title').text('ALL Classifier Devices'); + $('.modal-body').text(resp.Message); + $('#responsive-modal').modal('show'); + break; + default: + msg = "ALL Update Failed: " + resp.Message + Logging.logMessage("Core", "ALL", msg); + $('.modal-title').text('ALL Classifier Devices'); + $('.modal-body').text(msg); + $('#responsive-modal').modal('show'); + break; + } + }); + }, deleteData: function() { $.post(window.location.href, { "deleteData": 1 }, function(resp) { - console.log(resp) var resp = jQuery.parseJSON(resp); switch (resp.Response) { case "OK": $('#dataBlock').empty(); - $('#dataBlock').html("

Please upload your Acute Lymphoblastic Leukemia Image Database for Image Processing Dataset data.

"); + $('#dataBlock').html("

Please upload your test dataset.

"); break; default: break; @@ -38,6 +81,9 @@ var ALL = { $('#dataBlock').html(resp.Data); ALL.setOpacity(); Logging.logMessage("Core", "Forms", resp.Message); + $('.modal-title').text('Data Upload OK'); + $('.modal-body').text(resp.Message); + $('#responsive-modal').modal('show'); } else { Logging.logMessage("Core", "Forms", resp.Message); $('.modal-title').text('Data Upload Failed'); @@ -53,7 +99,6 @@ var ALL = { }); }, setOpacity: function() { - $('.classify').css("opacity", "1.0"); $('.classify').hover(function() { $(this).stop().animate({ opacity: 0.2 }, "fast"); @@ -64,7 +109,7 @@ var ALL = { }, classify: function(im) { - $('#imageView').html(""); + $('#imageView').html(""); $("#imName").text(im); var classification = ''; $("#imClass").html("Diagnosis: WAITING FOR RESPONSE"); @@ -73,9 +118,6 @@ var ALL = { var resp = jQuery.parseJSON(resp); switch (resp.Response) { case "OK": - console.log(im); - console.log(im.includes("_0")); - console.log(resp.Diagnosis); if (im.includes("_0") && resp.Diagnosis == "Negative") { classification = "True Negative"; } else if (im.includes("_0") && resp.Diagnosis == "Positive") { @@ -86,6 +128,11 @@ var ALL = { classification = "False Negative"; } $("#imClass").html("Diagnosis: " + resp.Diagnosis); + if (resp.Confidence) { + $("#imConf").html("Confidence: " + resp.Confidence); + } else { + $("#imConf").hide(); + } $("#imResult").html("Result: " + classification); break; default: @@ -97,6 +144,20 @@ var ALL = { }; $(document).ready(function() { + $('#all_classifier').validator().on('submit', function(e) { + if (!e.isDefaultPrevented()) { + e.preventDefault(); + ALL.Create(); + } + }); + + $('#all_classifier_update').validator().on('submit', function(e) { + if (!e.isDefaultPrevented()) { + e.preventDefault(); + ALL.Update(); + } + }); + $("#GeniSysAI").on("click", "#uploadData", function(e) { e.preventDefault(); $('#dataup').trigger('click'); @@ -112,7 +173,4 @@ $(document).ready(function() { ALL.classify($(this).attr("id")); }); - ALL.setOpacity(); - ALL.prepareUploadForm(); - }); \ No newline at end of file diff --git a/Root/var/www/html/AI/ALL/Classes/ALL.php b/Root/var/www/html/AI/ALL/Classes/ALL.php new file mode 100644 index 0000000..0e851b4 --- /dev/null +++ b/Root/var/www/html/AI/ALL/Classes/ALL.php @@ -0,0 +1,1687 @@ +_GeniSys = $_GeniSys; + $this->bcc = $this->getBlockchainConf(); + $this->web3 = $this->blockchainConnection(); + $this->contract = new Contract($this->web3->provider, $this->bcc["abi"]); + $this->icontract = new Contract($this->web3->provider, $this->bcc["iabi"]); + $this->checkBlockchainPermissions(); + endif; + $this->cb = $this->getContextBrokerConf(); + } + + public function setClassifierConfs() + { + $TId = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_NUMBER_INT); + $Model = $this->getModel($TId); + + $this->dataDir = "Data/" . $Model["context"]["Data"]["dataset"]["folder"] . "/"; + $this->dataDirFull = "/fserver/var/www/html/AI/AI//"; + $this->dataFiles = $this->dataDir . "*.jpg"; + $this->allowedFiles = ["jpg","JPG"]; + $this->api = $this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"])."/AI/AI/" . $Model["context"]["Data"]["proxy"]["endpoint"] . "/Inference"; + } + + public function getContextBrokerConf() + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM contextbroker + "); + $pdoQuery->execute(); + $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + return $response; + } + + private function createContextHeaders() + { + $basicAuth = $_SESSION["GeniSysAI"]["User"] . ":" . $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"]); + $basicAuth = base64_encode($basicAuth); + + return [ + "Content-Type: application/json", + 'Authorization: Basic '. $basicAuth + ]; + } + + private function contextBrokerRequest($method, $endpoint, $headers, $json) + { + $path = $this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/" . $this->cb["url"] . "/" . $endpoint; + + if($method == "GET"): + $ch = curl_init(); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_URL, $path); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "POST"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "PATCH"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + endif; + + return $body; + } + + public function getBlockchainConf() + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT blockchain.*, + contracts.contract, + contracts.abi, + icontracts.contract as icontract, + icontracts.abi as iabi + FROM blockchain blockchain + INNER JOIN contracts contracts + ON contracts.id = blockchain.dc + INNER JOIN contracts icontracts + ON icontracts.id = blockchain.ic + "); + $pdoQuery->execute(); + $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + return $response; + } + + private function blockchainConnection() + { + if(isSet($_SESSION["GeniSysAI"]["Active"])): + $web3 = new Web3($this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/Blockchain/API/", 30, $_SESSION["GeniSysAI"]["User"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"])); + return $web3; + endif; + } + + private function checkBlockchainPermissions() + { + $allowed = ""; + $errr = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->call("identifierAllowed", "User", $_SESSION["GeniSysAI"]["Identifier"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$allowed, &$errr) { + if ($err !== null) { + $allowed = "FAILED"; + $errr = $err; + return; + } + $allowed = $resp; + }); + if(!$allowed): + header('Location: /Logout'); + endif; + } + + private function unlockBlockchainAccount() + { + $response = ""; + $personal = $this->web3->personal; + $personal->unlockAccount($_SESSION["GeniSysAI"]["BC"]["BCUser"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["BC"]["BCPass"]), function ($err, $unlocked) use (&$response) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + if ($unlocked) { + $response = "OK"; + } else { + $response = "FAILED"; + } + }); + + return $response; + } + + private function lockBlockchainAccount() + { + $response = ""; + $personal = $this->web3->personal; + $personal->lockAccount($_SESSION["GeniSysAI"]["BC"]["BCUser"], function ($err, $unlocked) use (&$response) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + if ($unlocked) { + $response = "OK"; + } else { + $response = "FAILED"; + } + }); + + return $response; + } + + private function createBlockchainUser($pass) + { + $newAccount = ""; + $personal = $this->web3->personal; + $personal->newAccount($pass, function ($err, $account) use (&$newAccount) { + if ($err !== null) { + $newAccount = "FAILED!"; + return; + } + $newAccount = $account; + }); + + return $newAccount; + } + + private function getBlockchainBalance() + { + $nbalance = ""; + $this->web3->eth->getBalance($_SESSION["GeniSysAI"]["BC"]["BCUser"], function ($err, $balance) use (&$nbalance) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + $nbalance = $balance->toString(); + }); + + return Utils::fromWei($nbalance, 'ether')[0]; + } + + private function addAmqpUser($username, $key) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpu ( + `username`, + `pw` + ) VALUES ( + :username, + :pw + ) + "); + $query->execute([ + ':username' => $username, + ':pw' => $this->_GeniSys->_helpers->oEncrypt($key) + ]); + $amid = $this->_GeniSys->_secCon->lastInsertId(); + return $amid; + } + + private function addAmqpUserVh($uid, $vhost) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvh ( + `uid`, + `vhost` + ) VALUES ( + :uid, + :vhost + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost + ]); + } + + private function addAmqpVhPerm($uid, $vhost, $rtype, $rname, $permission) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvhr ( + `uid`, + `vhost`, + `rtype`, + `rname`, + `permission` + ) VALUES ( + :uid, + :vhost, + :rtype, + :rname, + :permission + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost, + ':rtype' => $rtype, + ':rname' => $rname, + ':permission' => $permission + ]); + } + + private function addAmqpVhTopic($uid, $vhost, $rtype, $rname, $permission, $rkey) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvhrt ( + `uid`, + `vhost`, + `rtype`, + `rname`, + `permission`, + `rkey` + ) VALUES ( + :uid, + :vhost, + :rtype, + :rname, + :permission, + :rkey + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost, + ':rtype' => $rtype, + ':rname' => $rname, + ':permission' => $permission, + ':rkey' => $rkey + ]); + } + + private function storeBlockchainTransaction($action, $hash, $device = 0, $application = 0) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + INSERT INTO transactions ( + `uid`, + `did`, + `aid`, + `action`, + `hash`, + `time` + ) VALUES ( + :uid, + :did, + :aid, + :action, + :hash, + :time + ) + "); + $pdoQuery->execute([ + ":uid" => $_SESSION["GeniSysAI"]["Uid"], + ":did" => $device, + ":aid" => $application, + ":action" => $action, + ':hash' => $this->_GeniSys->_helpers->oEncrypt($hash), + ":time" => time() + ]); + $txid = $this->_GeniSys->_secCon->lastInsertId(); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + return $txid; + } + + private function storeUserHistory($action, $hash, $location = 0, $zone = 0, $device = 0, $sensor = 0, $application = 0) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + INSERT INTO history ( + `uid`, + `tlid`, + `tzid`, + `tdid`, + `tsid`, + `taid`, + `action`, + `hash`, + `time` + ) VALUES ( + :uid, + :tlid, + :tzid, + :tdid, + :tsid, + :taid, + :action, + :hash, + :time + ) + "); + $pdoQuery->execute([ + ":uid" => $_SESSION["GeniSysAI"]["Uid"], + ":tlid" => $location, + ":tzid" => $zone, + ":tdid" => $device, + ":tsid" => $sensor, + ":taid" => $application, + ":action" => $action, + ":hash" => $hash, + ":time" => time() + ]); + $txid = $this->_GeniSys->_secCon->lastInsertId(); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + return $txid; + } + + public function checkLocation($lid) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT id + FROM mqttl + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $lid + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($location["id"]): + return True; + else: + return False; + endif; + } + + public function getLocation($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttl + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $location["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $location["pub"] . "?type=Location" . $attrs, $this->createContextHeaders(), []), true); + return $location; + } + + public function checkZone($zid) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT id + FROM mqttlz + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $zid + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($location["id"]): + return True; + else: + return False; + endif; + } + + public function getZone($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttlz + WHERE id = :id + ORDER BY id DESC + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $zone=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $zone["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $zone["pub"] . "?type=Zone" . $attrs, $this->createContextHeaders(), []), true); + return $zone; + } + + public function getDevices($limit = 0) + { + $limiter = ""; + if($limit != 0): + $limiter = "&limit=" . $limit; + endif; + + $devices = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "?type=Device&category=ALLClassifier".$limiter, $this->createContextHeaders(), []), true); + return $devices; + } + + public function getDevice($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttld + WHERE id = :id + ORDER BY id DESC + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $device=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $device["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $device["apub"] . "?type=Device" . $attrs, $this->createContextHeaders(), []), true); + return $device; + } + + public function getThing($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM things + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $thing=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $thing["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $thing["pub"] . "?type=Thing" . $attrs, $this->createContextHeaders(), []), true); + return $thing; + } + + public function getModel($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM models + WHERE id = :id + ORDER BY id DESC + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $model=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $device["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $model["pub"] . "?type=Model" . $attrs, $this->createContextHeaders(), []), true); + return $device; + } + + public function createDevice() + { + if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Location ID is required" + ]; + endif; + + if(!$this->checkLocation(filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Zone ID is required" + ]; + endif; + + if(!$this->checkZone(filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay zone does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Category is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ctype", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Model type is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device model is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system version is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Coordinates entity is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IP is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "MAC is required" + ]; + endif; + + if(!count($_POST["protocols"])): + return [ + "Response"=> "Failed", + "Message" => "At least one M2M protocol is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetUsed", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset used is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetLink", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset link is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetAuthor", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset author is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset folder is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "relatedPaper", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Related paper is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Device server port is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "endpoint", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Proxy endpoint is required" + ]; + endif; + + $unlocked = $this->unlockBlockchainAccount(); + + if($unlocked == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Unlocking HIAS Blockhain Account Failed!" + ]; + endif; + + $mqttUser = $this->_GeniSys->_helpers->generate_uuid(); + $mqttPass = $this->_GeniSys->_helpers->password(); + $mqttHash = create_hash($mqttPass); + + $pubKey = $this->_GeniSys->_helpers->generate_uuid(); + $privKey = $this->_GeniSys->_helpers->generateKey(32); + $privKeyHash = $this->_GeniSys->_helpers->createPasswordHash($privKey); + + $amqppubKey = $this->_GeniSys->_helpers->generate_uuid(); + $amqpprvKey = $this->_GeniSys->_helpers->generateKey(32); + $amqpKeyHash = $this->_GeniSys->_helpers->createPasswordHash($amqpprvKey); + + $bcPass = $this->_GeniSys->_helpers->password(); + + $lid = filter_input(INPUT_POST, 'lid', FILTER_SANITIZE_NUMBER_INT); + $location = $this->getLocation($lid); + + $zid = filter_input(INPUT_POST, 'zid', FILTER_SANITIZE_NUMBER_INT); + $zone = $this->getZone($zid); + + $ip = filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING); + $mac = filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING); + $bluetooth = filter_input(INPUT_POST, "bluetooth", FILTER_SANITIZE_STRING); + $name = filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING); + $coords = explode(",", filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)); + + mkdir("Data/" . filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING), 0777, true); + + $protocols = []; + foreach($_POST["protocols"] AS $key => $value): + $protocols[] = $value; + endforeach; + + $models = []; + if(isSet($_POST["ai"])): + foreach($_POST["ai"] AS $key => $value): + $model = $this->getModel($value)["context"]["Data"]; + $mname = $model["name"]["value"]; + unset($model["id"]); + unset($model["type"]); + unset($model["mid"]); + unset($model["name"]); + unset($model["description"]); + unset($model["network"]); + unset($model["language"]); + unset($model["framework"]); + unset($model["toolkit"]); + unset($model["dateCreated"]); + unset($model["dateModified"]); + $models[$mname] = $model; + endforeach; + endif; + + $sensors = []; + if(isSet($_POST["sensors"])): + foreach($_POST["sensors"] AS $key => $value): + $sensor = $this->getThing($value)["context"]["Data"]; + unset($sensor["id"]); + unset($sensor["type"]); + unset($sensor["category"]); + unset($sensor["description"]); + unset($sensor["thing"]); + unset($sensor["properties"]["image"]); + unset($sensor["dateCreated"]); + unset($sensor["dateModified"]); + $sensors[] = $sensor; + endforeach; + endif; + + $actuators = []; + if(isSet($_POST["actuators"])): + foreach($_POST["actuators"] AS $key => $value): + $actuator = $this->getThing($value)["context"]["Data"]; + unset($actuator["id"]); + unset($actuator["type"]); + unset($actuator["category"]); + unset($actuator["description"]); + unset($actuator["thing"]); + unset($actuator["properties"]["image"]); + unset($actuator["dateCreated"]); + unset($actuator["dateModeified"]); + $actuators[] = $actuator; + endforeach; + endif; + + $newBcUser = $this->createBlockchainUser($bcPass); + + if($newBcUser == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Creating New HIAS Blockhain Account Failed!" + ]; + endif; + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttld ( + `apub` + ) VALUES ( + :apub + ) + "); + $query->execute([ + ':apub' => $pubKey + ]); + $did = $this->_GeniSys->_secCon->lastInsertId(); + + $data = [ + "id" => $pubKey, + "type" => "Device", + "category" => [ + "value" => [filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)] + ], + "name" => [ + "value" => $name + ], + "description" => [ + "value" => filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING) + ], + "lid" => [ + "value" => $lid, + "entity" => $location["context"]["Data"]["id"] + ], + "zid" => [ + "value" => $zid, + "entity" => $zone["context"]["Data"]["id"] + ], + "did" => [ + "value" => $did + ], + "location" => [ + "type" => "geo:json", + "value" => [ + "type" => "Point", + "coordinates" => [floatval($coords[0]), floatval($coords[1])] + ] + ], + "agent" => [ + "url" => filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING) + ], + "paper" => [ + "title" => filter_input(INPUT_POST, "relatedPaper", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "relatedPaperAuthor", FILTER_SANITIZE_STRING), + "doi" => filter_input(INPUT_POST, "relatedPaperDOI", FILTER_SANITIZE_STRING), + "link" => filter_input(INPUT_POST, "relatedPaperLink", FILTER_SANITIZE_STRING) + ], + "dataset" => [ + "name" => filter_input(INPUT_POST, "datasetUsed", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "datasetAuthor", FILTER_SANITIZE_STRING), + "url" => filter_input(INPUT_POST, "datasetLink", FILTER_SANITIZE_STRING), + "folder" => filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING) + ], + "device" => [ + "type" => filter_input(INPUT_POST, "ctype", FILTER_SANITIZE_STRING), + "name" => filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING), + "model" => filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING) + ], + "proxy" => [ + "endpoint" => filter_input(INPUT_POST, "endpoint", FILTER_SANITIZE_STRING) + ], + "stream" => [ + "port" => filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING), + "file" => "" + ], + "socket" => [ + "port" => "" + ], + "os" => [ + "name" => filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING), + "version" => filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING) + ], + "protocols" => $protocols, + "status" => [ + "value" => "OFFLINE", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "keys" => [ + "public" => $pubKey, + "private" => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "blockchain" => [ + "address" => $newBcUser, + "password" => $this->_GeniSys->_helpers->oEncrypt($bcPass) + ], + "mqtt" => [ + "username" => $this->_GeniSys->_helpers->oEncrypt($mqttUser), + "password" => $this->_GeniSys->_helpers->oEncrypt($mqttPass), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "coap" => [ + "username" => "", + "password" => "" + ], + "amqp" => [ + "username" => $this->_GeniSys->_helpers->oEncrypt($amqppubKey), + "password" => $this->_GeniSys->_helpers->oEncrypt($amqpprvKey), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "batteryLevel" => [ + "value" => 0.00 + ], + "cpuUsage" => [ + "value" => 0.00 + ], + "memoryUsage" => [ + "value" => 0.00 + ], + "hddUsage" => [ + "value" => 0.00 + ], + "temperature" => [ + "value" => 0.00 + ], + "ip" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($ip), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "mac" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($mac), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "bluetooth" => [ + "address" => $bluetooth ? $this->_GeniSys->_helpers->oEncrypt($bluetooth) : "", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "ai" => $models, + "sensors" => $sensors, + "actuators" => $actuators, + "dateCreated" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateFirstUsed" => [ + "type" => "DateTime", + "value" => "" + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + + $response = json_decode($this->contextBrokerRequest("POST", $this->cb["entities_url"] . "?type=Device", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttu ( + `lid`, + `zid`, + `did`, + `uname`, + `pw` + ) VALUES ( + :lid, + :zid, + :did, + :uname, + :pw + ) + "); + $query->execute([ + ':lid' => $lid, + ':zid' => $zid, + ':did' => $did, + ':uname' => $mqttUser, + ':pw' => $mqttHash + ]); + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttua ( + `lid`, + `zid`, + `did`, + `username`, + `topic`, + `rw` + ) VALUES ( + :lid, + :zid, + :did, + :username, + :topic, + :rw + ) + "); + $query->execute(array( + ':lid' => $lid, + ':zid' => $zid, + ':did' => $did, + ':username' => $mqttUser, + ':topic' => $location["context"]["Data"]["id"] . "/Devices/" . $zone["context"]["Data"]["id"] . "/" . $pubKey . "/#", + ':rw' => 4 + )); + + $amid = $this->addAmqpUser($amqppubKey, $amqpKeyHash); + $this->addAmqpUserVh($amid, "iotJumpWay"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "exchange", "Core", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "exchange", "Core", "write"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Life", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Life", "write"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Statuses", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Statuses", "write"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "read", "Life"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "write", "Life"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "read", "Statuses"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "write", "Statuses"); + + $hash = ""; + $msg = ""; + $actionMsg = ""; + $balanceMessage = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("registerDevice", $pubKey, $newBcUser, $lid, $zid, $did, $name, $_SESSION["GeniSysAI"]["Uid"], time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg = " HIAS Blockchain registerDevice failed!\n" . $msg; + else: + $txid = $this->storeBlockchainTransaction("Register Device", $hash, $did); + $this->storeUserHistory("Register Device", $txid, $lid, $zid, $did); + $balance = $this->getBlockchainBalance(); + $actionMsg = " HIAS Blockchain registerDevice OK!\n"; + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; + endif; + + $this->icontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["icontract"]))->send("registerAuthorized", $newBcUser, ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg .= " HIAS Blockchain registerAuthorized failed! " . $msg; + else: + $txid = $this->storeBlockchainTransaction("iotJumpWay Register Authorized", $hash, $did); + $this->storeUserHistory("Register Authorized", $txid, $lid, $zid, $did); + $balance = $this->getBlockchainBalance(); + $actionMsg .= " HIAS Blockchain registerAuthorized OK!\n"; + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; + endif; + + return [ + "Response"=> "OK", + "Message" => "Device created!" . $actionMsg . $balanceMessage, + "LID" => filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT), + "ZID" => filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT), + "DID" => $did, + "MU" => $mqttUser, + "MP" => $mqttPass, + "BU" => $newBcUser, + "BP" => $bcPass, + "AppID" => $pubKey, + "AppKey" => $privKey + ]; + else: + return [ + "Response"=> "FAILED", + "Message" => "Device creating failed" + ]; + endif; + } + + public function updateDevice() + { + if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Location ID is required" + ]; + endif; + + if(!$this->checkLocation(filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Zone ID is required" + ]; + endif; + + if(!$this->checkZone(filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay zone does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Category is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ctype", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Model type is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device model is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system version is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Coordinates entity is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IP is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "MAC is required" + ]; + endif; + + if(!count($_POST["protocols"])): + return [ + "Response"=> "Failed", + "Message" => "At least one M2M protocol is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetUsed", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset used is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetLink", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset link is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetAuthor", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset author is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset folder is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "relatedPaper", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Related paper is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Device server port is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "endpoint", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Proxy endpoint is required" + ]; + endif; + + $unlocked = $this->unlockBlockchainAccount(); + + if($unlocked == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Unlocking HIAS Blockhain Account Failed!" + ]; + endif; + + $ip = filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING); + $mac = filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING); + $bluetooth = filter_input(INPUT_POST, "bluetooth", FILTER_SANITIZE_STRING); + $name = filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING); + $status = filter_input(INPUT_POST, "status", FILTER_SANITIZE_STRING); + $coords = explode(",", filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)); + + $did = filter_input(INPUT_GET, "device", FILTER_SANITIZE_NUMBER_INT); + $device = $this->getDevice($did); + + $lid = filter_input(INPUT_POST, 'lid', FILTER_SANITIZE_NUMBER_INT); + $location = $this->getLocation($lid); + + $zid = filter_input(INPUT_POST, 'zid', FILTER_SANITIZE_NUMBER_INT); + $zone = $this->getZone($zid); + + $protocols = []; + foreach($_POST["protocols"] AS $key => $value): + $protocols[] = $value; + endforeach; + + $models = []; + if(isSet($_POST["ai"])): + foreach($_POST["ai"] AS $key => $value): + $model = $this->getModel($value)["context"]["Data"]; + $mname = $model["name"]["value"]; + unset($model["id"]); + unset($model["type"]); + unset($model["mid"]); + unset($model["name"]); + unset($model["description"]); + unset($model["network"]); + unset($model["language"]); + unset($model["framework"]); + unset($model["toolkit"]); + unset($model["dateCreated"]); + unset($model["dateModified"]); + $models[$mname] = $model; + endforeach; + endif; + + $sensors = []; + if(isSet($_POST["sensors"])): + foreach($_POST["sensors"] AS $key => $value): + $sensor = $this->getThing($value)["context"]["Data"]; + unset($sensor["id"]); + unset($sensor["type"]); + unset($sensor["category"]); + unset($sensor["description"]); + unset($sensor["thing"]); + unset($sensor["properties"]["image"]); + unset($sensor["dateCreated"]); + unset($sensor["dateModified"]); + $sensors[] = $sensor; + endforeach; + endif; + + $actuators = []; + if(isSet($_POST["actuators"])): + foreach($_POST["actuators"] AS $key => $value): + $actuator = $this->getThing($value)["context"]["Data"]; + unset($actuator["id"]); + unset($actuator["type"]); + unset($actuator["category"]); + unset($actuator["description"]); + unset($actuator["thing"]); + unset($actuator["properties"]["image"]); + unset($actuator["dateCreated"]); + unset($actuator["dateModeified"]); + $actuators[] = $actuator; + endforeach; + endif; + + if($device["context"]["Data"]["lid"]["value"] != $lid): + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttu + SET lid = :lid + WHERE did = :did + "); + $query->execute([ + ':lid' => $lid, + ':did' => $did + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttua + SET lid = :lid + WHERE did = :did + "); + $query->execute([ + ':lid' => $lid, + ':did' => $did + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttua + SET topic = :topicN + WHERE did = :did + & topic = :topic + "); + $query->execute([ + ':topicN' => $device["context"]["Data"]["lid"]["entity"] . "/ " . $device["context"]["Data"]["zid"]["entity"] . "/Devices/" . $device["context"]["Data"]["id"] . "/#", + ':did' => $did, + ':topic' => $location["context"]["Data"]["id"] . "/Devices/" . $zone["context"]["Data"]["id"] . "/Devices/" . $device["context"]["Data"]["id"] . "/#" + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + endif; + + $data = [ + "category" => [ + "value" => [filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)] + ], + "name" => [ + "value" => $name + ], + "description" => [ + "value" => filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING) + ], + "lid" => [ + "value" => $lid, + "entity" => $location["context"]["Data"]["id"] + ], + "zid" => [ + "value" => $zid, + "entity" => $zone["context"]["Data"]["id"] + ], + "location" => [ + "type" => "geo:json", + "value" => [ + "type" => "Point", + "coordinates" => [floatval($coords[0]), floatval($coords[1])] + ] + ], + "agent" => [ + "url" => filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING) + ], + "paper" => [ + "title" => filter_input(INPUT_POST, "relatedPaper", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "relatedPaperAuthor", FILTER_SANITIZE_STRING), + "doi" => filter_input(INPUT_POST, "relatedPaperDOI", FILTER_SANITIZE_STRING), + "link" => filter_input(INPUT_POST, "relatedPaperLink", FILTER_SANITIZE_STRING) + ], + "dataset" => [ + "name" => filter_input(INPUT_POST, "datasetUsed", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "datasetAuthor", FILTER_SANITIZE_STRING), + "url" => filter_input(INPUT_POST, "datasetLink", FILTER_SANITIZE_STRING), + "folder" => filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING) + ], + "device" => [ + "type" => filter_input(INPUT_POST, "ctype", FILTER_SANITIZE_STRING), + "name" => filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING), + "model" => filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING) + ], + "proxy" => [ + "endpoint" => filter_input(INPUT_POST, "endpoint", FILTER_SANITIZE_STRING) + ], + "stream" => [ + "port" => filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING), + "file" => "" + ], + "socket" => [ + "port" => "" + ], + "os" => [ + "name" => filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING), + "version" => filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING) + ], + "protocols" => $protocols, + "ip" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($ip), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "mac" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($mac), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "bluetooth" => [ + "address" => $bluetooth ? $this->_GeniSys->_helpers->oEncrypt($bluetooth) : "", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "ai" => $models, + "sensors" => $sensors, + "actuators" => $actuators, + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $device["context"]["Data"]["id"] . "/attrs?type=Device", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + + $hash = ""; + $msg = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("updateDevice", $device["context"]["Data"]["id"], "Device", $lid, $zid, $did, $name, $device["context"]["Data"]["status"]["value"], time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + $balance = ""; + $balanceMessage = ""; + $actionMsg = ""; + if($hash == "FAILED"): + $actionMsg = " HIAS Blockchain updateDevice failed! " . $msg; + else: + $txid = $this->storeBlockchainTransaction("Update Device", $hash, $did); + $this->storeUserHistory("Updated Device", $txid, $lid, $zid, $did); + $balance = $this->getBlockchainBalance(); + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; + endif; + + $device = $this->getDevice($did); + + return [ + "Response"=> "OK", + "Message" => "Device updated!" . $actionMsg . $balanceMessage, + "Schema" => $device["context"]["Data"] + ]; + else: + return [ + "Response"=> "Failed", + "Message" => "There was a problem updating this device context data!" + ]; + endif; + } + + public function deleteData() + { + $images = glob($this->dataFiles); + foreach( $images as $image ): + unlink($image); + endforeach; + + return [ + "Response" => "OK", + "Message" => "Deleted Acute Lymphoblastic Leukemia Image Database for Image Processing Dataset" + ]; + + } + + public function uploadData() + { + $dataCells = ''; + if(is_array($_FILES) && !empty($_FILES['alldata'])): + foreach($_FILES['alldata']['name'] as $key => $filename): + $file_name = explode(".", $filename); + if(in_array($file_name[1], $this->allowedFiles)): + $sourcePath = $_FILES["alldata"]["tmp_name"][$key]; + $targetPath = $this->dataDir . $filename; + if(!move_uploaded_file($sourcePath, $targetPath)): + return [ + "Response" => "FAILED", + "Message" => "Upload failed " . $targetPath + ]; + endif; + else: + return [ + "Response" => "FAILED", + "Message" => "Please upload jpg files" + ]; + endif; + endforeach; + + $images = glob($this->dataFiles); + $count = 1; + foreach($images as $image): + $dataCells .= "
"; + if($count%6 == 0): + $dataCells .= "
"; + endif; + $count++; + endforeach; + + else: + return [ + "Response" => "FAILED", + "Message" => "You must upload some images (jpg)" + ]; + endif; + + return [ + "Response" => "OK", + "Message" => "Data upload OK!", + "Data" => $dataCells + ]; + + } + + public function classifyData() + { + $file = $this->dataDirFull . filter_input(INPUT_POST, "im", FILTER_SANITIZE_STRING); + $mime = mime_content_type($file); + $info = pathinfo($file); + $name = $info['basename']; + $toSend = new CURLFile($file, $mime, $name); + + $headers = [ + 'Authorization: Basic '. base64_encode($_SESSION["GeniSysAI"]["User"] . ":" . $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"])) + ]; + + $ch = curl_init($this->api); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_POSTFIELDS, [ + 'file'=> $toSend, + ]); + + $resp = curl_exec($ch); + + return json_decode($resp, true); + + } + + } + + $ALL = new ALL($_GeniSys); + + if(filter_input(INPUT_POST, "create_all_classifier", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($ALL->createDevice())); + endif; + + if(filter_input(INPUT_POST, "update_all_classifier", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($ALL->updateDevice())); + endif; + + if(filter_input(INPUT_POST, "reset_mqtt", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($ALL->resetMqtt())); + endif; + + if(filter_input(INPUT_POST, "reset_key", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($ALL->resetDvcKey())); + endif; + + if(filter_input(INPUT_POST, "get_tlife", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($ALL->getLife())); + endif; + + if(filter_input(INPUT_POST, "deleteData", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($ALL->deleteData())); + endif; + + if(filter_input(INPUT_POST, "uploadAllData", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($ALL->uploadData())); + endif; + + if(filter_input(INPUT_POST, "classifyData", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($ALL->classifyData())); + endif; diff --git a/Root/var/www/html/AI/ALL/Classify.php b/Root/var/www/html/AI/ALL/Classify.php new file mode 100644 index 0000000..9377add --- /dev/null +++ b/Root/var/www/html/AI/ALL/Classify.php @@ -0,0 +1,202 @@ + "AI", + "SubPageID" => "AIALL" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/ALL/Classes/ALL.php'; + +$_GeniSysAi->checkSession(); + +$Locations = $iotJumpWay->getLocations(); +$Zones = $iotJumpWay->getZones(); +$Devices = $ALL->getDevices(); + +$TId = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_NUMBER_INT); +$Device = $iotJumpWay->getDevice($TId); + +list($dev1On, $dev1Off) = $iotJumpWay->getStatusShow($Device["context"]["Data"]["status"]["value"]); + +$ALL->setClassifierConfs(); + +?> + + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
ALL Classifier #
+
+ +
+
+
+
+ + + +
+ + dataFiles); + $count = 1; + if(count($images)): + foreach( $images as $image ): + echo "
"; + if($count%6 == 0): + echo"
"; + endif; + $count++; + endforeach; + else: + echo "

Please upload your test dataset.

"; + endif; + ?> + +
+
+
+
+
+
+
+
+
+
Online Offline
+
+ +
+  %    +  %    +  %    +  %    +  °C +
+
+
+
+
+
+
+
+
Diagnosis Results
+
+
+
+
+
+
+
+ +
+
+

+
+
+ +
+ +
+
+
+
+
+ +
+ + + +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/AI/ALL/Create.php b/Root/var/www/html/AI/ALL/Create.php new file mode 100644 index 0000000..97d8503 --- /dev/null +++ b/Root/var/www/html/AI/ALL/Create.php @@ -0,0 +1,422 @@ + "AI", + "SubPageID" => "AIALL" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/ALL/Classes/ALL.php'; +include dirname(__FILE__) . '/../../AI/Classes/AI.php'; + +$_GeniSysAi->checkSession(); + +$Zones = $iotJumpWay->getZones(); +$Devices = $iotJumpWay->getDevices(); + +?> + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
Create ALL Classifier Device
+
+
+
+
+
+
+
+
+
+
+
+ + + Name of device +
+
+ + + Description of device +
+
+ + + Device category +
+
+ + + Type of ALL model +
+
+ + + Device IoT Agent +
+
+ + + Name of hardware device +
+
+ + + Name of hardware manufacturer +
+
+ + + Hardware model +
+
+ + + Operating system name +
+
+ + + Operating system manufacturer +
+
+ + + Operating system version +
+
+ + + Supported Communication Protocols +
+
+ +
+
+ Device Sensors + +
+
+ +
+
+ Device Actuators + +
+
+ + + Device AI Models +
+
+ + +
+
+
+
+ + + Dataset used to train and test model +
+
+ + + Dataset link +
+
+ + + Dataset author +
+
+ + + Dataset folder to be created to hold the test data +
+
+ + + Related research paper +
+
+ + + Related research paper author +
+
+ + + Related research paper DOI +
+
+ + + Related research paper link +
+
+ + + Location of device +
+
+ + + Zone of device +
+
+ + + iotJumpWay Device coordinates +
+
+ + + IP of device +
+
+ + + MAC of device +
+
+ + + Bluetooth address of device +
+
+ + + Server port of ALL classifier device +
+
+ + + Endpoint name of NGINX reverse proxy +
+
+
+
+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + + + diff --git a/Root/var/www/html/Data-Analysis/COVID-19/Data/__init__.py b/Root/var/www/html/AI/ALL/Data/.keep similarity index 100% rename from Root/var/www/html/Data-Analysis/COVID-19/Data/__init__.py rename to Root/var/www/html/AI/ALL/Data/.keep diff --git a/Root/var/www/html/AI/ALL/Device.php b/Root/var/www/html/AI/ALL/Device.php new file mode 100644 index 0000000..2e2962e --- /dev/null +++ b/Root/var/www/html/AI/ALL/Device.php @@ -0,0 +1,955 @@ + "AI", + "SubPageID" => "AIALL" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/ALL/Classes/ALL.php'; +include dirname(__FILE__) . '/../../AI/Classes/AI.php'; + +$_GeniSysAi->checkSession(); + +$Locations = $iotJumpWay->getLocations(); +$Zones = $iotJumpWay->getZones(); +$Devices = $ALL->getDevices(); + +$TId = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_NUMBER_INT); +$Device = $iotJumpWay->getDevice($TId); + +list($dev1On, $dev1Off) = $iotJumpWay->getStatusShow($Device["context"]["Data"]["status"]["value"]); + +?> + + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
ALL Classifier Device #
+
+
+
+
+
+
+
+
+
+
+
+ + "> + Name of device +
+
+ + "> + Description of device +
+
+ + + Device category +
+
+ + + Type of ALL model +
+
+ + + Device IoT Agent +
+
+ + "> + Name of hardware device +
+
+ + "> + Name of hardware manufacturer +
+
+ + "> + Hardware model +
+
+ + "> + Operating system name +
+
+ + "> + Operating system manufacturer +
+
+ + "> + Operating system version +
+
+ + + Supported Communication Protocols +
+
+ +
+
+ $value): + ?> + +
+
+ + " required> +
+
+ +
+
+ + +
+ Device Sensors + +
+
+ +
+
+ $value): + ?> + +
+
+ + " required> +
+
+ +
+
+ + +
+ Device Actuators + +
+
+ + + Device AI Models +
+
+ + +
+
+
+
+ + "> + Dataset used to train and test model +
+
+ + "> + Dataset link +
+
+ + "> + Dataset author +
+
+ + "> + Dataset folder on HIAS +
+
+ + "> + Related research paper +
+
+ + "> + Related research paper author +
+
+ + "> + Related research paper DOI +
+
+ + "> + Related research paper link +
+
+ + + Location of device +
+
+ + + Zone of device +
+
+ + , "> + iotJumpWay Device coordinates +
+
+ + _helpers->oDecrypt($Device["context"]["Data"]["ip"]["value"]) : ""; ?>"> + IP of device +
+
+ + _helpers->oDecrypt($Device["context"]["Data"]["mac"]["value"]) : ""; ?>"> + MAC of device +
+
+ + _helpers->oDecrypt($Device["context"]["Data"]["bluetooth"]["address"]) : ""; ?>"> + Bluetooth address of device +
+
+ + "> + Port of ALL stream +
+
+ + "> + Endpoint name of NGINX reverse proxy +
+
+ +

+
+
+ +

+
+
+ +

+
+
+
+
+
+
+
+

+
+
+
+
Device Schema
+
+
+
+
+
+
+
+ "; ?> "; ?> +
+
+
+

+
+ +
+
+
+
+ + + + + + + + + + + + retrieveDeviceHistory($Device["context"]["Data"]["did"]["value"], 5); + if(count($history)): + foreach($history as $key => $value): + if($value["uid"]): + $user = $_GeniSysAi->getUser($value["uid"]); + $userDetails = "User ID #" . $value["uid"] . " (" . $user["name"] . ") "; + endif; + ?> + + + + + + + + + + + +
IDActionReceiptTime
# + + + /Zones//Devices//Transaction/"># + + NA + + + + +
+
+
+
+
+

+
+ +
+
+
+
+ + + + + + + + + + + + retrieveDeviceTransactions($Device["context"]["Data"]["did"]["value"], 5); + if(count($transactions)): + foreach($transactions as $key => $value): + if($value["uid"]): + $user = $_GeniSysAi->getUser($value["uid"]); + $userDetails = "User ID #" . $value["uid"] . " (" . $user["name"] . ") "; + endif; + ?> + + + + + + + + + + + +
IDActionReceiptTime
#/Zones//Devices//Transaction/">#
+
+
+
+
+

+
+
+
+
Device iotJumpWay Statuses
+
+ +
+
+
+
+
+
+ + + + + + + + + + + retrieveDeviceStatuses($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + ?> + + + + + + + + + + +
IDStatusTime
#_id;?>Status;?>Time;?>
+
+
+
+
+

+
+
+
+
Device iotJumpWay Life
+
+ +
+
+
+
+
+
+ + + + + + + + + + + retrieveDeviceLife($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + ?> + + + + + + + + + + +
IDDetailsTime
#_id;?> + CPU: Data->CPU;?>%
+ Memory: Data->Memory;?>%
+ Diskspace: Data->Diskspace;?>%
+ Temperature: Data->Temperature;?>°C
+ Latitude: Data->Latitude;?>
+ Longitude: Data->Longitude;?>
+
Time;?>
+
+
+
+
+

+
+
+
+
Device iotJumpWay Sensors
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + retrieveDeviceSensors($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + $location = $iotJumpWay->getLocation($value->Location); + ?> + + + + + + + + + + + + +
IDTypeSensorValueMessageTime
#_id;?>Type;?>Sensor;?> + Sensor == "Facial API" || $value->Sensor == "Foscam Camera" || $value->Sensor == "USB Camera") && is_array($value->Value)): + foreach($value->Value AS $key => $val): + echo $val[0] == 0 ? "Identification: Intruder
" :"Identification: User #" . $val[0] . "
"; + echo "Distance: " . $val[1] . "
"; + echo "Message: " . $val[2] . "

"; + endforeach; + else: + echo $value->Value; + endif; + ?> + +
Message;?>Time;?>
+
+
+
+
+

+
+
+
+
Device iotJumpWay Commands
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + retrieveDeviceCommands($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + $location = $iotJumpWay->getLocation($value->Location); + ?> + + + + + + + + + + + +
IDDetailsStatusTime
#_id;?>Location: #Location;?> - Status;?>Time;?>
+
+
+
+
+
+
+
+
+
+
+
Online Offline
+
+ +
+  %    +  %    +  %    +  %    +  °C +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+

+

Last Updated:

+
+
+
+
+
+
+
+
+
+
+ +
+

+
+
+
+
+
+
+
+
+ +
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["mqtt"]["username"]); ?>

+
+
+
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["mqtt"]["password"]); ?> +

Last Updated:

+

+
+
+
+
+
+
+
+
+ +
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["amqp"]["username"]) : ""; ?>

+
+
+
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["amqp"]["password"]) : ""; ?> +

Last Updated:

+

+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/AI/ALL/index.php b/Root/var/www/html/AI/ALL/index.php new file mode 100644 index 0000000..ae733bd --- /dev/null +++ b/Root/var/www/html/AI/ALL/index.php @@ -0,0 +1,178 @@ + "AI", + "SubPageID" => "AIALL" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/ALL/Classes/ALL.php'; + +$_GeniSysAi->checkSession(); +$Devices = $ALL->getDevices(); + +?> + + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
Acute Lymphoblastic Leukemia (ALL) Models
+
+
+
+
+
+
+

The Acute Lymphoblastic Leukemia (ALL) Models are a range of computer vision models for detecting Acute Lymphoblastic Leukemia, designed by the Peter Moss Acute Lymphoblastic & Lymphoblastic AI Research Project team. The models are designed to be used on constrained devices making them suitable for IoT networks. These models use a variety of programming languages, frameworks and hardware providing. You can download our ALL models from the Peter Moss Acute Lymphoblastic & Lymphoblastic Leukemia AI Research Project Github repository, instructions for installation are provided in the tutorials. To find out more about the Peter Moss Acute Lymphoblastic & Lymphoblastic AI Research Project, you can visit the research project homepage on our website, or visit the official website.

+
+
+
+
+
+
+

In addition to using Acute Lymphoblastic Leukemia detection models created by the Peter Moss Acute Lymphoblastic & Lymphoblastic AI Research Project team, you can develop your own models and connect them up to the HIAS network.

+
+
+
+
+
+
+
Acute Lymphoblastic Leukemia Detection Devices
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + $value): + ?> + + + + + + + + + + + +
IDDETAILSSTATUSACTION
# + Name:
+ Zone: # +
+
"> + +
+
"> Edit | /Classify"> Classify
+
+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/AI/AML/Classes/AML.js b/Root/var/www/html/AI/AML/Classes/AML.js new file mode 100644 index 0000000..db2883b --- /dev/null +++ b/Root/var/www/html/AI/AML/Classes/AML.js @@ -0,0 +1,176 @@ +var ALL = { + Create: function() { + $.post(window.location.href, $("#aml_classifier").serialize(), function(resp) { + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + GeniSys.ResetForm("aml_classifier"); + $('.modal-title').text('ALL Classifier Devices'); + $('.modal-body').html("HIAS ALL Classifier Device ID #" + resp.GDID + " created! Please save the API keys safely. The device's credentials are provided below. The credentials can be reset in the GeniSyAI Security Devices area.

Device ID: " + resp.DID + "
MQTT User: " + resp.MU + "
MQTT Password: " + resp.MP + "

Blockchain User: " + resp.BU + "
Blockchain Pass: " + resp.BP + "

App ID: " + resp.AppID + "
App Key: " + resp.AppKey + "

" + resp.Message); + $('#responsive-modal').modal('show'); + Logging.logMessage("Core", "Forms", "Device ID #" + resp.DID + " created!"); + break; + default: + msg = "ALL Create Failed: " + resp.Message + Logging.logMessage("Core", "ALL", msg); + $('.modal-title').text('ALL Classifier Devices'); + $('.modal-body').text(msg); + $('#responsive-modal').modal('show'); + break; + } + }); + }, + Update: function() { + $.post(window.location.href, $("#aml_classifier_update").serialize(), function(resp) { + console.log(resp) + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + var fjson = JSON.stringify(resp.Schema, null, '\t'); + window.parent.$('#schema').html(fjson); + Logging.logMessage("Core", "Forms", "Device Update OK"); + $('.modal-title').text('ALL Classifier Devices'); + $('.modal-body').text(resp.Message); + $('#responsive-modal').modal('show'); + break; + default: + msg = "ALL Update Failed: " + resp.Message + Logging.logMessage("Core", "ALL", msg); + $('.modal-title').text('ALL Classifier Devices'); + $('.modal-body').text(msg); + $('#responsive-modal').modal('show'); + break; + } + }); + }, + deleteData: function() { + $.post(window.location.href, { "deleteData": 1 }, function(resp) { + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + $('#dataBlock').empty(); + $('#dataBlock').html("

Please upload your test dataset.

"); + break; + default: + break; + } + }); + }, + prepareUploadForm: function() { + + var upper = document.querySelector('#dataup'), + form = new FormData(), + xhr = new XMLHttpRequest(); + + form.append('uploadAllData', 1); + + upper.addEventListener('change', function(event) { + event.preventDefault(); + + var files = this.files; + for (var i = 0, n = files.length; i < n; i++) { + var file = files[i]; + + form.append('amldata[]', file, file.name); + + xhr.onload = function() { + if (xhr.status === 200) { + var resp = jQuery.parseJSON(xhr.response); + if (resp.Response === "OK") { + $('#dataBlock').empty(); + $('#dataBlock').html(resp.Data); + ALL.setOpacity(); + Logging.logMessage("Core", "Forms", resp.Message); + $('.modal-title').text('Data Upload OK'); + $('.modal-body').text(resp.Message); + $('#responsive-modal').modal('show'); + } else { + Logging.logMessage("Core", "Forms", resp.Message); + $('.modal-title').text('Data Upload Failed'); + $('.modal-body').text(resp.Message); + $('#responsive-modal').modal('show'); + } + } + } + + xhr.open('POST', ''); + xhr.send(form); + } + }); + }, + setOpacity: function() { + $('.classify').css("opacity", "1.0"); + $('.classify').hover(function() { + $(this).stop().animate({ opacity: 0.2 }, "fast"); + }, + function() { + $(this).stop().animate({ opacity: 1.0 }, "fast"); + }); + }, + classify: function(im) { + + $('#imageView').html(""); + $("#imName").text(im); + var classification = ''; + $("#imClass").html("Diagnosis: WAITING FOR RESPONSE"); + $("#imResult").html("Result: WAITING FOR RESPONSE"); + $.post(window.location.href, { "classifyData": 1, "im": im }, function(resp) { + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + if (im.includes("_0") && resp.Diagnosis == "Negative") { + classification = "True Negative"; + } else if (im.includes("_0") && resp.Diagnosis == "Positive") { + classification = "False Positive"; + } else if (im.includes("_1") && resp.Diagnosis == "Positive") { + classification = "True Positive"; + } else if (im.includes("_1") && resp.Diagnosis == "Negative") { + classification = "False Negative"; + } + $("#imClass").html("Diagnosis: " + resp.Diagnosis); + if (resp.Confidence) { + $("#imConf").html("Confidence: " + resp.Confidence); + } else { + $("#imConf").hide(); + } + $("#imResult").html("Result: " + classification); + break; + default: + break; + } + }); + + } +}; +$(document).ready(function() { + + $('#aml_classifier').validator().on('submit', function(e) { + if (!e.isDefaultPrevented()) { + e.preventDefault(); + ALL.Create(); + } + }); + + $('#aml_classifier_update').validator().on('submit', function(e) { + if (!e.isDefaultPrevented()) { + e.preventDefault(); + ALL.Update(); + } + }); + + $("#GeniSysAI").on("click", "#uploadData", function(e) { + e.preventDefault(); + $('#dataup').trigger('click'); + }); + + $("#GeniSysAI").on("click", "#deleteData", function(e) { + e.preventDefault(); + ALL.deleteData(); + }); + + $("#GeniSysAI").on("click", ".classify", function(e) { + e.preventDefault(); + ALL.classify($(this).attr("id")); + }); + +}); \ No newline at end of file diff --git a/Root/var/www/html/AI/AML/Classes/AML.php b/Root/var/www/html/AI/AML/Classes/AML.php new file mode 100644 index 0000000..909b751 --- /dev/null +++ b/Root/var/www/html/AI/AML/Classes/AML.php @@ -0,0 +1,1687 @@ +_GeniSys = $_GeniSys; + $this->bcc = $this->getBlockchainConf(); + $this->web3 = $this->blockchainConnection(); + $this->contract = new Contract($this->web3->provider, $this->bcc["abi"]); + $this->icontract = new Contract($this->web3->provider, $this->bcc["iabi"]); + $this->checkBlockchainPermissions(); + endif; + $this->cb = $this->getContextBrokerConf(); + } + + public function setClassifierConfs() + { + $TId = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_NUMBER_INT); + $Device = $this->getDevice($TId); + + $this->dataDir = "Data/" . $Device["context"]["Data"]["dataset"]["folder"] . "/"; + $this->dataDirFull = "/fserver/var/www/html/AI/AML//"; + $this->dataFiles = $this->dataDir . "*.jpg"; + $this->amlowedFiles = ["jpg","JPG"]; + $this->api = $this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"])."/AI/AML/" . $Device["context"]["Data"]["proxy"]["endpoint"] . "/Inference"; + } + + public function getContextBrokerConf() + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM contextbroker + "); + $pdoQuery->execute(); + $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + return $response; + } + + private function createContextHeaders() + { + $basicAuth = $_SESSION["GeniSysAI"]["User"] . ":" . $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"]); + $basicAuth = base64_encode($basicAuth); + + return [ + "Content-Type: application/json", + 'Authorization: Basic '. $basicAuth + ]; + } + + private function contextBrokerRequest($method, $endpoint, $headers, $json) + { + $path = $this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/" . $this->cb["url"] . "/" . $endpoint; + + if($method == "GET"): + $ch = curl_init(); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_URL, $path); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "POST"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "PATCH"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + endif; + + return $body; + } + + public function getBlockchainConf() + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT blockchain.*, + contracts.contract, + contracts.abi, + icontracts.contract as icontract, + icontracts.abi as iabi + FROM blockchain blockchain + INNER JOIN contracts contracts + ON contracts.id = blockchain.dc + INNER JOIN contracts icontracts + ON icontracts.id = blockchain.ic + "); + $pdoQuery->execute(); + $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + return $response; + } + + private function blockchainConnection() + { + if(isSet($_SESSION["GeniSysAI"]["Active"])): + $web3 = new Web3($this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/Blockchain/API/", 30, $_SESSION["GeniSysAI"]["User"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"])); + return $web3; + endif; + } + + private function checkBlockchainPermissions() + { + $amlowed = ""; + $errr = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->call("identifierAllowed", "User", $_SESSION["GeniSysAI"]["Identifier"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$amlowed, &$errr) { + if ($err !== null) { + $amlowed = "FAILED"; + $errr = $err; + return; + } + $amlowed = $resp; + }); + if(!$amlowed): + header('Location: /Logout'); + endif; + } + + private function unlockBlockchainAccount() + { + $response = ""; + $personal = $this->web3->personal; + $personal->unlockAccount($_SESSION["GeniSysAI"]["BC"]["BCUser"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["BC"]["BCPass"]), function ($err, $unlocked) use (&$response) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + if ($unlocked) { + $response = "OK"; + } else { + $response = "FAILED"; + } + }); + + return $response; + } + + private function lockBlockchainAccount() + { + $response = ""; + $personal = $this->web3->personal; + $personal->lockAccount($_SESSION["GeniSysAI"]["BC"]["BCUser"], function ($err, $unlocked) use (&$response) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + if ($unlocked) { + $response = "OK"; + } else { + $response = "FAILED"; + } + }); + + return $response; + } + + private function createBlockchainUser($pass) + { + $newAccount = ""; + $personal = $this->web3->personal; + $personal->newAccount($pass, function ($err, $account) use (&$newAccount) { + if ($err !== null) { + $newAccount = "FAILED!"; + return; + } + $newAccount = $account; + }); + + return $newAccount; + } + + private function getBlockchainBalance() + { + $nbalance = ""; + $this->web3->eth->getBalance($_SESSION["GeniSysAI"]["BC"]["BCUser"], function ($err, $balance) use (&$nbalance) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + $nbalance = $balance->toString(); + }); + + return Utils::fromWei($nbalance, 'ether')[0]; + } + + private function addAmqpUser($username, $key) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpu ( + `username`, + `pw` + ) VALUES ( + :username, + :pw + ) + "); + $query->execute([ + ':username' => $username, + ':pw' => $this->_GeniSys->_helpers->oEncrypt($key) + ]); + $amid = $this->_GeniSys->_secCon->lastInsertId(); + return $amid; + } + + private function addAmqpUserVh($uid, $vhost) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvh ( + `uid`, + `vhost` + ) VALUES ( + :uid, + :vhost + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost + ]); + } + + private function addAmqpVhPerm($uid, $vhost, $rtype, $rname, $permission) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvhr ( + `uid`, + `vhost`, + `rtype`, + `rname`, + `permission` + ) VALUES ( + :uid, + :vhost, + :rtype, + :rname, + :permission + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost, + ':rtype' => $rtype, + ':rname' => $rname, + ':permission' => $permission + ]); + } + + private function addAmqpVhTopic($uid, $vhost, $rtype, $rname, $permission, $rkey) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvhrt ( + `uid`, + `vhost`, + `rtype`, + `rname`, + `permission`, + `rkey` + ) VALUES ( + :uid, + :vhost, + :rtype, + :rname, + :permission, + :rkey + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost, + ':rtype' => $rtype, + ':rname' => $rname, + ':permission' => $permission, + ':rkey' => $rkey + ]); + } + + private function storeBlockchainTransaction($action, $hash, $device = 0, $application = 0) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + INSERT INTO transactions ( + `uid`, + `did`, + `aid`, + `action`, + `hash`, + `time` + ) VALUES ( + :uid, + :did, + :aid, + :action, + :hash, + :time + ) + "); + $pdoQuery->execute([ + ":uid" => $_SESSION["GeniSysAI"]["Uid"], + ":did" => $device, + ":aid" => $application, + ":action" => $action, + ':hash' => $this->_GeniSys->_helpers->oEncrypt($hash), + ":time" => time() + ]); + $txid = $this->_GeniSys->_secCon->lastInsertId(); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + return $txid; + } + + private function storeUserHistory($action, $hash, $location = 0, $zone = 0, $device = 0, $sensor = 0, $application = 0) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + INSERT INTO history ( + `uid`, + `tlid`, + `tzid`, + `tdid`, + `tsid`, + `taid`, + `action`, + `hash`, + `time` + ) VALUES ( + :uid, + :tlid, + :tzid, + :tdid, + :tsid, + :taid, + :action, + :hash, + :time + ) + "); + $pdoQuery->execute([ + ":uid" => $_SESSION["GeniSysAI"]["Uid"], + ":tlid" => $location, + ":tzid" => $zone, + ":tdid" => $device, + ":tsid" => $sensor, + ":taid" => $application, + ":action" => $action, + ":hash" => $hash, + ":time" => time() + ]); + $txid = $this->_GeniSys->_secCon->lastInsertId(); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + return $txid; + } + + public function checkLocation($lid) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT id + FROM mqttl + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $lid + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($location["id"]): + return True; + else: + return False; + endif; + } + + public function getLocation($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttl + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $location["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $location["pub"] . "?type=Location" . $attrs, $this->createContextHeaders(), []), true); + return $location; + } + + public function checkZone($zid) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT id + FROM mqttlz + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $zid + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($location["id"]): + return True; + else: + return False; + endif; + } + + public function getZone($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttlz + WHERE id = :id + ORDER BY id DESC + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $zone=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $zone["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $zone["pub"] . "?type=Zone" . $attrs, $this->createContextHeaders(), []), true); + return $zone; + } + + public function getDevices($limit = 0) + { + $limiter = ""; + if($limit != 0): + $limiter = "&limit=" . $limit; + endif; + + $devices = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "?type=Device&category=AMLClassifier".$limiter, $this->createContextHeaders(), []), true); + return $devices; + } + + public function getDevice($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttld + WHERE id = :id + ORDER BY id DESC + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $device=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $device["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $device["apub"] . "?type=Device" . $attrs, $this->createContextHeaders(), []), true); + return $device; + } + + public function getThing($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM things + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $thing=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $thing["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $thing["pub"] . "?type=Thing" . $attrs, $this->createContextHeaders(), []), true); + return $thing; + } + + public function getModel($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM models + WHERE id = :id + ORDER BY id DESC + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $model=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $device["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $model["pub"] . "?type=Model" . $attrs, $this->createContextHeaders(), []), true); + return $device; + } + + public function createDevice() + { + if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Location ID is required" + ]; + endif; + + if(!$this->checkLocation(filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Zone ID is required" + ]; + endif; + + if(!$this->checkZone(filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay zone does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Category is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ctype", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Model type is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device model is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system version is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Coordinates entity is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IP is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "MAC is required" + ]; + endif; + + if(!count($_POST["protocols"])): + return [ + "Response"=> "Failed", + "Message" => "At least one M2M protocol is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetUsed", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset used is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetLink", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset link is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetAuthor", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset author is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset folder is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "relatedPaper", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Related paper is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Device server port is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "endpoint", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Proxy endpoint is required" + ]; + endif; + + $unlocked = $this->unlockBlockchainAccount(); + + if($unlocked == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Unlocking HIAS Blockhain Account Failed!" + ]; + endif; + + $mqttUser = $this->_GeniSys->_helpers->generate_uuid(); + $mqttPass = $this->_GeniSys->_helpers->password(); + $mqttHash = create_hash($mqttPass); + + $pubKey = $this->_GeniSys->_helpers->generate_uuid(); + $privKey = $this->_GeniSys->_helpers->generateKey(32); + $privKeyHash = $this->_GeniSys->_helpers->createPasswordHash($privKey); + + $amqppubKey = $this->_GeniSys->_helpers->generate_uuid(); + $amqpprvKey = $this->_GeniSys->_helpers->generateKey(32); + $amqpKeyHash = $this->_GeniSys->_helpers->createPasswordHash($amqpprvKey); + + $bcPass = $this->_GeniSys->_helpers->password(); + + $lid = filter_input(INPUT_POST, 'lid', FILTER_SANITIZE_NUMBER_INT); + $location = $this->getLocation($lid); + + $zid = filter_input(INPUT_POST, 'zid', FILTER_SANITIZE_NUMBER_INT); + $zone = $this->getZone($zid); + + $ip = filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING); + $mac = filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING); + $bluetooth = filter_input(INPUT_POST, "bluetooth", FILTER_SANITIZE_STRING); + $name = filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING); + $coords = explode(",", filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)); + + mkdir("Data/" . filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING), 0777, true); + + $protocols = []; + foreach($_POST["protocols"] AS $key => $value): + $protocols[] = $value; + endforeach; + + $models = []; + if(isSet($_POST["ai"])): + foreach($_POST["ai"] AS $key => $value): + $model = $this->getModel($value)["context"]["Data"]; + $mname = $model["name"]["value"]; + unset($model["id"]); + unset($model["type"]); + unset($model["mid"]); + unset($model["name"]); + unset($model["description"]); + unset($model["network"]); + unset($model["language"]); + unset($model["framework"]); + unset($model["toolkit"]); + unset($model["dateCreated"]); + unset($model["dateModified"]); + $models[$mname] = $model; + endforeach; + endif; + + $sensors = []; + if(isSet($_POST["sensors"])): + foreach($_POST["sensors"] AS $key => $value): + $sensor = $this->getThing($value)["context"]["Data"]; + unset($sensor["id"]); + unset($sensor["type"]); + unset($sensor["category"]); + unset($sensor["description"]); + unset($sensor["thing"]); + unset($sensor["properties"]["image"]); + unset($sensor["dateCreated"]); + unset($sensor["dateModified"]); + $sensors[] = $sensor; + endforeach; + endif; + + $actuators = []; + if(isSet($_POST["actuators"])): + foreach($_POST["actuators"] AS $key => $value): + $actuator = $this->getThing($value)["context"]["Data"]; + unset($actuator["id"]); + unset($actuator["type"]); + unset($actuator["category"]); + unset($actuator["description"]); + unset($actuator["thing"]); + unset($actuator["properties"]["image"]); + unset($actuator["dateCreated"]); + unset($actuator["dateModeified"]); + $actuators[] = $actuator; + endforeach; + endif; + + $newBcUser = $this->createBlockchainUser($bcPass); + + if($newBcUser == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Creating New HIAS Blockhain Account Failed!" + ]; + endif; + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttld ( + `apub` + ) VALUES ( + :apub + ) + "); + $query->execute([ + ':apub' => $pubKey + ]); + $did = $this->_GeniSys->_secCon->lastInsertId(); + + $data = [ + "id" => $pubKey, + "type" => "Device", + "category" => [ + "value" => [filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)] + ], + "name" => [ + "value" => $name + ], + "description" => [ + "value" => filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING) + ], + "lid" => [ + "value" => $lid, + "entity" => $location["context"]["Data"]["id"] + ], + "zid" => [ + "value" => $zid, + "entity" => $zone["context"]["Data"]["id"] + ], + "did" => [ + "value" => $did + ], + "location" => [ + "type" => "geo:json", + "value" => [ + "type" => "Point", + "coordinates" => [floatval($coords[0]), floatval($coords[1])] + ] + ], + "agent" => [ + "url" => filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING) + ], + "paper" => [ + "title" => filter_input(INPUT_POST, "relatedPaper", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "relatedPaperAuthor", FILTER_SANITIZE_STRING), + "doi" => filter_input(INPUT_POST, "relatedPaperDOI", FILTER_SANITIZE_STRING), + "link" => filter_input(INPUT_POST, "relatedPaperLink", FILTER_SANITIZE_STRING) + ], + "dataset" => [ + "name" => filter_input(INPUT_POST, "datasetUsed", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "datasetAuthor", FILTER_SANITIZE_STRING), + "url" => filter_input(INPUT_POST, "datasetLink", FILTER_SANITIZE_STRING), + "folder" => filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING) + ], + "device" => [ + "type" => filter_input(INPUT_POST, "ctype", FILTER_SANITIZE_STRING), + "name" => filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING), + "model" => filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING) + ], + "proxy" => [ + "endpoint" => filter_input(INPUT_POST, "endpoint", FILTER_SANITIZE_STRING) + ], + "stream" => [ + "port" => filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING), + "file" => "" + ], + "socket" => [ + "port" => "" + ], + "os" => [ + "name" => filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING), + "version" => filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING) + ], + "protocols" => $protocols, + "status" => [ + "value" => "OFFLINE", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "keys" => [ + "public" => $pubKey, + "private" => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "blockchain" => [ + "address" => $newBcUser, + "password" => $this->_GeniSys->_helpers->oEncrypt($bcPass) + ], + "mqtt" => [ + "username" => $this->_GeniSys->_helpers->oEncrypt($mqttUser), + "password" => $this->_GeniSys->_helpers->oEncrypt($mqttPass), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "coap" => [ + "username" => "", + "password" => "" + ], + "amqp" => [ + "username" => $this->_GeniSys->_helpers->oEncrypt($amqppubKey), + "password" => $this->_GeniSys->_helpers->oEncrypt($amqpprvKey), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "batteryLevel" => [ + "value" => 0.00 + ], + "cpuUsage" => [ + "value" => 0.00 + ], + "memoryUsage" => [ + "value" => 0.00 + ], + "hddUsage" => [ + "value" => 0.00 + ], + "temperature" => [ + "value" => 0.00 + ], + "ip" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($ip), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "mac" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($mac), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "bluetooth" => [ + "address" => $bluetooth ? $this->_GeniSys->_helpers->oEncrypt($bluetooth) : "", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "ai" => $models, + "sensors" => $sensors, + "actuators" => $actuators, + "dateCreated" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateFirstUsed" => [ + "type" => "DateTime", + "value" => "" + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + + $response = json_decode($this->contextBrokerRequest("POST", $this->cb["entities_url"] . "?type=Device", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttu ( + `lid`, + `zid`, + `did`, + `uname`, + `pw` + ) VALUES ( + :lid, + :zid, + :did, + :uname, + :pw + ) + "); + $query->execute([ + ':lid' => $lid, + ':zid' => $zid, + ':did' => $did, + ':uname' => $mqttUser, + ':pw' => $mqttHash + ]); + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttua ( + `lid`, + `zid`, + `did`, + `username`, + `topic`, + `rw` + ) VALUES ( + :lid, + :zid, + :did, + :username, + :topic, + :rw + ) + "); + $query->execute(array( + ':lid' => $lid, + ':zid' => $zid, + ':did' => $did, + ':username' => $mqttUser, + ':topic' => $location["context"]["Data"]["id"] . "/Devices/" . $zone["context"]["Data"]["id"] . "/" . $pubKey . "/#", + ':rw' => 4 + )); + + $amid = $this->addAmqpUser($amqppubKey, $amqpKeyHash); + $this->addAmqpUserVh($amid, "iotJumpWay"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "exchange", "Core", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "exchange", "Core", "write"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Life", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Life", "write"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Statuses", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Statuses", "write"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "read", "Life"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "write", "Life"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "read", "Statuses"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "write", "Statuses"); + + $hash = ""; + $msg = ""; + $actionMsg = ""; + $balanceMessage = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("registerDevice", $pubKey, $newBcUser, $lid, $zid, $did, $name, $_SESSION["GeniSysAI"]["Uid"], time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg = " HIAS Blockchain registerDevice failed!\n" . $msg; + else: + $txid = $this->storeBlockchainTransaction("Register Device", $hash, $did); + $this->storeUserHistory("Register Device", $txid, $lid, $zid, $did); + $balance = $this->getBlockchainBalance(); + $actionMsg = " HIAS Blockchain registerDevice OK!\n"; + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; + endif; + + $this->icontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["icontract"]))->send("registerAuthorized", $newBcUser, ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg .= " HIAS Blockchain registerAuthorized failed! " . $msg; + else: + $txid = $this->storeBlockchainTransaction("iotJumpWay Register Authorized", $hash, $did); + $this->storeUserHistory("Register Authorized", $txid, $lid, $zid, $did); + $balance = $this->getBlockchainBalance(); + $actionMsg .= " HIAS Blockchain registerAuthorized OK!\n"; + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; + endif; + + return [ + "Response"=> "OK", + "Message" => "Device created!" . $actionMsg . $balanceMessage, + "LID" => filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT), + "ZID" => filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT), + "DID" => $did, + "MU" => $mqttUser, + "MP" => $mqttPass, + "BU" => $newBcUser, + "BP" => $bcPass, + "AppID" => $pubKey, + "AppKey" => $privKey + ]; + else: + return [ + "Response"=> "FAILED", + "Message" => "Device creating failed" + ]; + endif; + } + + public function updateDevice() + { + if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Location ID is required" + ]; + endif; + + if(!$this->checkLocation(filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Zone ID is required" + ]; + endif; + + if(!$this->checkZone(filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay zone does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Category is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ctype", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Model type is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device model is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system version is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Coordinates entity is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IP is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "MAC is required" + ]; + endif; + + if(!count($_POST["protocols"])): + return [ + "Response"=> "Failed", + "Message" => "At least one M2M protocol is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetUsed", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset used is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetLink", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset link is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetAuthor", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset author is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset folder is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "relatedPaper", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Related paper is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Device server port is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "endpoint", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Proxy endpoint is required" + ]; + endif; + + $unlocked = $this->unlockBlockchainAccount(); + + if($unlocked == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Unlocking HIAS Blockhain Account Failed!" + ]; + endif; + + $ip = filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING); + $mac = filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING); + $bluetooth = filter_input(INPUT_POST, "bluetooth", FILTER_SANITIZE_STRING); + $name = filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING); + $status = filter_input(INPUT_POST, "status", FILTER_SANITIZE_STRING); + $coords = explode(",", filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)); + + $did = filter_input(INPUT_GET, "device", FILTER_SANITIZE_NUMBER_INT); + $device = $this->getDevice($did); + + $lid = filter_input(INPUT_POST, 'lid', FILTER_SANITIZE_NUMBER_INT); + $location = $this->getLocation($lid); + + $zid = filter_input(INPUT_POST, 'zid', FILTER_SANITIZE_NUMBER_INT); + $zone = $this->getZone($zid); + + $protocols = []; + foreach($_POST["protocols"] AS $key => $value): + $protocols[] = $value; + endforeach; + + $models = []; + if(isSet($_POST["ai"])): + foreach($_POST["ai"] AS $key => $value): + $model = $this->getModel($value)["context"]["Data"]; + $mname = $model["name"]["value"]; + unset($model["id"]); + unset($model["type"]); + unset($model["mid"]); + unset($model["name"]); + unset($model["description"]); + unset($model["network"]); + unset($model["language"]); + unset($model["framework"]); + unset($model["toolkit"]); + unset($model["dateCreated"]); + unset($model["dateModified"]); + $models[$mname] = $model; + endforeach; + endif; + + $sensors = []; + if(isSet($_POST["sensors"])): + foreach($_POST["sensors"] AS $key => $value): + $sensor = $this->getThing($value)["context"]["Data"]; + unset($sensor["id"]); + unset($sensor["type"]); + unset($sensor["category"]); + unset($sensor["description"]); + unset($sensor["thing"]); + unset($sensor["properties"]["image"]); + unset($sensor["dateCreated"]); + unset($sensor["dateModified"]); + $sensors[] = $sensor; + endforeach; + endif; + + $actuators = []; + if(isSet($_POST["actuators"])): + foreach($_POST["actuators"] AS $key => $value): + $actuator = $this->getThing($value)["context"]["Data"]; + unset($actuator["id"]); + unset($actuator["type"]); + unset($actuator["category"]); + unset($actuator["description"]); + unset($actuator["thing"]); + unset($actuator["properties"]["image"]); + unset($actuator["dateCreated"]); + unset($actuator["dateModeified"]); + $actuators[] = $actuator; + endforeach; + endif; + + if($device["context"]["Data"]["lid"]["value"] != $lid): + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttu + SET lid = :lid + WHERE did = :did + "); + $query->execute([ + ':lid' => $lid, + ':did' => $did + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttua + SET lid = :lid + WHERE did = :did + "); + $query->execute([ + ':lid' => $lid, + ':did' => $did + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttua + SET topic = :topicN + WHERE did = :did + & topic = :topic + "); + $query->execute([ + ':topicN' => $device["context"]["Data"]["lid"]["entity"] . "/ " . $device["context"]["Data"]["zid"]["entity"] . "/Devices/" . $device["context"]["Data"]["id"] . "/#", + ':did' => $did, + ':topic' => $location["context"]["Data"]["id"] . "/Devices/" . $zone["context"]["Data"]["id"] . "/Devices/" . $device["context"]["Data"]["id"] . "/#" + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + endif; + + $data = [ + "category" => [ + "value" => [filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)] + ], + "name" => [ + "value" => $name + ], + "description" => [ + "value" => filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING) + ], + "lid" => [ + "value" => $lid, + "entity" => $location["context"]["Data"]["id"] + ], + "zid" => [ + "value" => $zid, + "entity" => $zone["context"]["Data"]["id"] + ], + "location" => [ + "type" => "geo:json", + "value" => [ + "type" => "Point", + "coordinates" => [floatval($coords[0]), floatval($coords[1])] + ] + ], + "agent" => [ + "url" => filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING) + ], + "paper" => [ + "title" => filter_input(INPUT_POST, "relatedPaper", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "relatedPaperAuthor", FILTER_SANITIZE_STRING), + "doi" => filter_input(INPUT_POST, "relatedPaperDOI", FILTER_SANITIZE_STRING), + "link" => filter_input(INPUT_POST, "relatedPaperLink", FILTER_SANITIZE_STRING) + ], + "dataset" => [ + "name" => filter_input(INPUT_POST, "datasetUsed", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "datasetAuthor", FILTER_SANITIZE_STRING), + "url" => filter_input(INPUT_POST, "datasetLink", FILTER_SANITIZE_STRING), + "folder" => filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING) + ], + "device" => [ + "type" => filter_input(INPUT_POST, "ctype", FILTER_SANITIZE_STRING), + "name" => filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING), + "model" => filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING) + ], + "proxy" => [ + "endpoint" => filter_input(INPUT_POST, "endpoint", FILTER_SANITIZE_STRING) + ], + "stream" => [ + "port" => filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING), + "file" => "" + ], + "socket" => [ + "port" => "" + ], + "os" => [ + "name" => filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING), + "version" => filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING) + ], + "protocols" => $protocols, + "ip" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($ip), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "mac" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($mac), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "bluetooth" => [ + "address" => $bluetooth ? $this->_GeniSys->_helpers->oEncrypt($bluetooth) : "", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "ai" => $models, + "sensors" => $sensors, + "actuators" => $actuators, + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $device["context"]["Data"]["id"] . "/attrs?type=Device", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + + $hash = ""; + $msg = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("updateDevice", $device["context"]["Data"]["id"], "Device", $lid, $zid, $did, $name, $device["context"]["Data"]["status"]["value"], time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + $balance = ""; + $balanceMessage = ""; + $actionMsg = ""; + if($hash == "FAILED"): + $actionMsg = " HIAS Blockchain updateDevice failed! " . $msg; + else: + $txid = $this->storeBlockchainTransaction("Update Device", $hash, $did); + $this->storeUserHistory("Updated Device", $txid, $lid, $zid, $did); + $balance = $this->getBlockchainBalance(); + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; + endif; + + $device = $this->getDevice($did); + + return [ + "Response"=> "OK", + "Message" => "Device updated!" . $actionMsg . $balanceMessage, + "Schema" => $device["context"]["Data"] + ]; + else: + return [ + "Response"=> "Failed", + "Message" => "There was a problem updating this device context data!" + ]; + endif; + } + + public function deleteData() + { + $images = glob($this->dataFiles); + foreach( $images as $image ): + unlink($image); + endforeach; + + return [ + "Response" => "OK", + "Message" => "Deleted Acute Lymphoblastic Leukemia Image Database for Image Processing Dataset" + ]; + + } + + public function uploadData() + { + $dataCells = ''; + if(is_array($_FILES) && !empty($_FILES['amldata'])): + foreach($_FILES['amldata']['name'] as $key => $filename): + $file_name = explode(".", $filename); + if(in_array($file_name[1], $this->amlowedFiles)): + $sourcePath = $_FILES["amldata"]["tmp_name"][$key]; + $targetPath = $this->dataDir . $filename; + if(!move_uploaded_file($sourcePath, $targetPath)): + return [ + "Response" => "FAILED", + "Message" => "Upload failed " . $targetPath + ]; + endif; + else: + return [ + "Response" => "FAILED", + "Message" => "Please upload jpg files" + ]; + endif; + endforeach; + + $images = glob($this->dataFiles); + $count = 1; + foreach($images as $image): + $dataCells .= "
"; + if($count%6 == 0): + $dataCells .= "
"; + endif; + $count++; + endforeach; + + else: + return [ + "Response" => "FAILED", + "Message" => "You must upload some images (jpg)" + ]; + endif; + + return [ + "Response" => "OK", + "Message" => "Data upload OK!", + "Data" => $dataCells + ]; + + } + + public function classifyData() + { + $file = $this->dataDirFull . filter_input(INPUT_POST, "im", FILTER_SANITIZE_STRING); + $mime = mime_content_type($file); + $info = pathinfo($file); + $name = $info['basename']; + $toSend = new CURLFile($file, $mime, $name); + + $headers = [ + 'Authorization: Basic '. base64_encode($_SESSION["GeniSysAI"]["User"] . ":" . $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"])) + ]; + + $ch = curl_init($this->api); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_POSTFIELDS, [ + 'file'=> $toSend, + ]); + + $resp = curl_exec($ch); + + return json_decode($resp, true); + + } + + } + + $AML = new AML($_GeniSys); + + if(filter_input(INPUT_POST, "create_aml_classifier", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($AML->createDevice())); + endif; + + if(filter_input(INPUT_POST, "update_aml_classifier", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($AML->updateDevice())); + endif; + + if(filter_input(INPUT_POST, "reset_mqtt", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($AML->resetMqtt())); + endif; + + if(filter_input(INPUT_POST, "reset_key", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($AML->resetDvcKey())); + endif; + + if(filter_input(INPUT_POST, "get_tlife", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($AML->getLife())); + endif; + + if(filter_input(INPUT_POST, "deleteData", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($AML->deleteData())); + endif; + + if(filter_input(INPUT_POST, "uploadAllData", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($AML->uploadData())); + endif; + + if(filter_input(INPUT_POST, "classifyData", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($AML->classifyData())); + endif; diff --git a/Root/var/www/html/AI/AML/Classify.php b/Root/var/www/html/AI/AML/Classify.php new file mode 100644 index 0000000..0f03d44 --- /dev/null +++ b/Root/var/www/html/AI/AML/Classify.php @@ -0,0 +1,200 @@ + "AI", + "SubPageID" => "AIAML" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/AML/Classes/AML.php'; + +$_GeniSysAi->checkSession(); + +$Locations = $iotJumpWay->getLocations(); +$Zones = $iotJumpWay->getZones(); +$Devices = $AML->getDevices(); + +$TId = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_NUMBER_INT); +$Device = $iotJumpWay->getDevice($TId); + +list($dev1On, $dev1Off) = $iotJumpWay->getStatusShow($Device["context"]["Data"]["status"]["value"]); + +$AML->setClassifierConfs(); + +?> + + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
AML Classifier #
+
+ +
+
+
+
+ + + +
+ + dataFiles); + $count = 1; + if(count($images)): + foreach( $images as $image ): + echo "
"; + if($count%6 == 0): + echo"
"; + endif; + $count++; + endforeach; + else: + echo "

Please upload your test dataset.

"; + endif; + ?> + +
+
+
+
+
+
+
+
+
+
Online Offline
+
+ +
+  %    +  %    +  %    +  %    +  °C +
+
+
+
+
+
+
+
+
Diagnosis Results
+
+
+
+
+
+
+
+ +
+
+

+
+
+ +
+ +
+
+
+
+
+ +
+ + + +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/AI/AML/Create.php b/Root/var/www/html/AI/AML/Create.php new file mode 100644 index 0000000..ff41465 --- /dev/null +++ b/Root/var/www/html/AI/AML/Create.php @@ -0,0 +1,422 @@ + "AI", + "SubPageID" => "AIAML" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/AML/Classes/AML.php'; +include dirname(__FILE__) . '/../../AI/Classes/AI.php'; + +$_GeniSysAi->checkSession(); + +$Zones = $iotJumpWay->getZones(); +$Devices = $iotJumpWay->getDevices(); + +?> + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
Create AML Classifier Device
+
+
+
+
+
+
+
+
+
+
+
+ + + Name of device +
+
+ + + Description of device +
+
+ + + Device category +
+
+ + + Type of AML model +
+
+ + + Device IoT Agent +
+
+ + + Name of hardware device +
+
+ + + Name of hardware manufacturer +
+
+ + + Hardware model +
+
+ + + Operating system name +
+
+ + + Operating system manufacturer +
+
+ + + Operating system version +
+
+ + + Supported Communication Protocols +
+
+ +
+
+ Device Sensors + +
+
+ +
+
+ Device Actuators + +
+
+ + + Device AI Models +
+
+ + +
+
+
+
+ + + Dataset used to train and test model +
+
+ + + Dataset link +
+
+ + + Dataset author +
+
+ + + Dataset folder to be created to hold the test data +
+
+ + + Related research paper +
+
+ + + Related research paper author +
+
+ + + Related research paper DOI +
+
+ + + Related research paper link +
+
+ + + Location of device +
+
+ + + Zone of device +
+
+ + + iotJumpWay Device coordinates +
+
+ + + IP of device +
+
+ + + MAC of device +
+
+ + + Bluetooth address of device +
+
+ + + Server port of AML classifier device +
+
+ + + Endpoint name of NGINX reverse proxy +
+
+
+
+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + + + diff --git a/Root/var/www/html/Detection/ALL/CNN/Data/__init__.py b/Root/var/www/html/AI/AML/Data/.keep similarity index 100% rename from Root/var/www/html/Detection/ALL/CNN/Data/__init__.py rename to Root/var/www/html/AI/AML/Data/.keep diff --git a/Root/var/www/html/AI/AML/Device.php b/Root/var/www/html/AI/AML/Device.php new file mode 100644 index 0000000..801f519 --- /dev/null +++ b/Root/var/www/html/AI/AML/Device.php @@ -0,0 +1,955 @@ + "AI", + "SubPageID" => "AIAML" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/AML/Classes/AML.php'; +include dirname(__FILE__) . '/../../AI/Classes/AI.php'; + +$_GeniSysAi->checkSession(); + +$Locations = $iotJumpWay->getLocations(); +$Zones = $iotJumpWay->getZones(); +$Devices = $AML->getDevices(); + +$TId = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_NUMBER_INT); +$Device = $iotJumpWay->getDevice($TId); + +list($dev1On, $dev1Off) = $iotJumpWay->getStatusShow($Device["context"]["Data"]["status"]["value"]); + +?> + + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
AML Classifier Device #
+
+
+
+
+
+
+
+
+
+
+
+ + "> + Name of device +
+
+ + "> + Description of device +
+
+ + + Device category +
+
+ + + Type of AML model +
+
+ + + Device IoT Agent +
+
+ + "> + Name of hardware device +
+
+ + "> + Name of hardware manufacturer +
+
+ + "> + Hardware model +
+
+ + "> + Operating system name +
+
+ + "> + Operating system manufacturer +
+
+ + "> + Operating system version +
+
+ + + Supported Communication Protocols +
+
+ +
+
+ $value): + ?> + +
+
+ + " required> +
+
+ +
+
+ + +
+ Device Sensors + +
+
+ +
+
+ $value): + ?> + +
+
+ + " required> +
+
+ +
+
+ + +
+ Device Actuators + +
+
+ + + Device AI Models +
+
+ + +
+
+
+
+ + "> + Dataset used to train and test model +
+
+ + "> + Dataset link +
+
+ + "> + Dataset author +
+
+ + "> + Dataset folder on HIAS +
+
+ + "> + Related research paper +
+
+ + "> + Related research paper author +
+
+ + "> + Related research paper DOI +
+
+ + "> + Related research paper link +
+
+ + + Location of device +
+
+ + + Zone of device +
+
+ + , "> + iotJumpWay Device coordinates +
+
+ + _helpers->oDecrypt($Device["context"]["Data"]["ip"]["value"]) : ""; ?>"> + IP of device +
+
+ + _helpers->oDecrypt($Device["context"]["Data"]["mac"]["value"]) : ""; ?>"> + MAC of device +
+
+ + _helpers->oDecrypt($Device["context"]["Data"]["bluetooth"]["address"]) : ""; ?>"> + Bluetooth address of device +
+
+ + "> + Port of AML stream +
+
+ + "> + Endpoint name of NGINX reverse proxy +
+
+ +

+
+
+ +

+
+
+ +

+
+
+
+
+
+
+
+

+
+
+
+
Device Schema
+
+
+
+
+
+
+
+ "; ?> "; ?> +
+
+
+

+
+ +
+
+
+
+ + + + + + + + + + + + retrieveDeviceHistory($Device["context"]["Data"]["did"]["value"], 5); + if(count($history)): + foreach($history as $key => $value): + if($value["uid"]): + $user = $_GeniSysAi->getUser($value["uid"]); + $userDetails = "User ID #" . $value["uid"] . " (" . $user["name"] . ") "; + endif; + ?> + + + + + + + + + + + +
IDActionReceiptTime
# + + + /Zones//Devices//Transaction/"># + + NA + + + + +
+
+
+
+
+

+
+ +
+
+
+
+ + + + + + + + + + + + retrieveDeviceTransactions($Device["context"]["Data"]["did"]["value"], 5); + if(count($transactions)): + foreach($transactions as $key => $value): + if($value["uid"]): + $user = $_GeniSysAi->getUser($value["uid"]); + $userDetails = "User ID #" . $value["uid"] . " (" . $user["name"] . ") "; + endif; + ?> + + + + + + + + + + + +
IDActionReceiptTime
#/Zones//Devices//Transaction/">#
+
+
+
+
+

+
+
+
+
Device iotJumpWay Statuses
+
+ +
+
+
+
+
+
+ + + + + + + + + + + retrieveDeviceStatuses($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + ?> + + + + + + + + + + +
IDStatusTime
#_id;?>Status;?>Time;?>
+
+
+
+
+

+
+
+
+
Device iotJumpWay Life
+
+ +
+
+
+
+
+
+ + + + + + + + + + + retrieveDeviceLife($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + ?> + + + + + + + + + + +
IDDetailsTime
#_id;?> + CPU: Data->CPU;?>%
+ Memory: Data->Memory;?>%
+ Diskspace: Data->Diskspace;?>%
+ Temperature: Data->Temperature;?>°C
+ Latitude: Data->Latitude;?>
+ Longitude: Data->Longitude;?>
+
Time;?>
+
+
+
+
+

+
+
+
+
Device iotJumpWay Sensors
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + retrieveDeviceSensors($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + $location = $iotJumpWay->getLocation($value->Location); + ?> + + + + + + + + + + + + +
IDTypeSensorValueMessageTime
#_id;?>Type;?>Sensor;?> + Sensor == "Facial API" || $value->Sensor == "Foscam Camera" || $value->Sensor == "USB Camera") && is_array($value->Value)): + foreach($value->Value AS $key => $val): + echo $val[0] == 0 ? "Identification: Intruder
" :"Identification: User #" . $val[0] . "
"; + echo "Distance: " . $val[1] . "
"; + echo "Message: " . $val[2] . "

"; + endforeach; + else: + echo $value->Value; + endif; + ?> + +
Message;?>Time;?>
+
+
+
+
+

+
+
+
+
Device iotJumpWay Commands
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + retrieveDeviceCommands($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + $location = $iotJumpWay->getLocation($value->Location); + ?> + + + + + + + + + + + +
IDDetailsStatusTime
#_id;?>Location: #Location;?> - Status;?>Time;?>
+
+
+
+
+
+
+
+
+
+
+
Online Offline
+
+ +
+  %    +  %    +  %    +  %    +  °C +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+

+

Last Updated:

+
+
+
+
+
+
+
+
+
+
+ +
+

+
+
+
+
+
+
+
+
+ +
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["mqtt"]["username"]); ?>

+
+
+
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["mqtt"]["password"]); ?> +

Last Updated:

+

+
+
+
+
+
+
+
+
+ +
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["amqp"]["username"]) : ""; ?>

+
+
+
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["amqp"]["password"]) : ""; ?> +

Last Updated:

+

+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/AI/AML/index.php b/Root/var/www/html/AI/AML/index.php new file mode 100644 index 0000000..d09b012 --- /dev/null +++ b/Root/var/www/html/AI/AML/index.php @@ -0,0 +1,178 @@ + "AI", + "SubPageID" => "AIAML" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/AML/Classes/AML.php'; + +$_GeniSysAi->checkSession(); +$Devices = $AML->getDevices(); + +?> + + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
Acute Myeloid Leukemia (AML) Models
+
+
+
+
+
+
+

The Acute Myeloid Leukemia (AML) Models are a range of computer vision models for detecting Acute Myeloid Leukemia, designed by the Peter Moss Acute Myeloid & Lymphoblastic Research Project team. The models are designed to be used on constrained devices making them suitable for IoT networks. These models use a variety of programming languages, frameworks and hardware providing. You can download our AML models from the Peter Moss Acute Myeloid & Lymphoblastic Leukemia AI Research Project Github repository, instructions for installation are provided in the tutorials. To find out more about the Peter Moss Acute Myeloid & Lymphoblastic Research Project, you can visit the research project homepage on our website, or visit the official website.

+
+
+
+
+
+
+

In addition to using Acute Myleoid Leukemia detection models created by the Peter Moss Acute Lymphoblastic & Lymphoblastic AI Research Project team, you can develop your own models and connect them up to the HIAS network.

+
+
+
+
+
+
+
Acute Myeloid Leukemia Detection Devices
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + $value): + ?> + + + + + + + + + + + +
IDDETAILSSTATUSACTION
# + Name:
+ Zone: # +
+
"> + +
+
"> Edit | /Classify"> Classify
+
+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/AI/COVID/Classes/COVID.js b/Root/var/www/html/AI/COVID/Classes/COVID.js new file mode 100644 index 0000000..6e9c743 --- /dev/null +++ b/Root/var/www/html/AI/COVID/Classes/COVID.js @@ -0,0 +1,179 @@ +var COVID = { + Create: function() { + $.post(window.location.href, $("#aml_classifier").serialize(), function(resp) { + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + GeniSys.ResetForm("aml_classifier"); + $('.modal-title').text('COVID Classifier Devices'); + $('.modal-body').html("HIAS COVID Classifier Device ID #" + resp.GDID + " created! Please save the API keys safely. The device's credentials are provided below. The credentials can be reset in the GeniSyAI Security Devices area.

Device ID: " + resp.DID + "
MQTT User: " + resp.MU + "
MQTT Password: " + resp.MP + "

Blockchain User: " + resp.BU + "
Blockchain Pass: " + resp.BP + "

App ID: " + resp.AppID + "
App Key: " + resp.AppKey + "

" + resp.Message); + $('#responsive-modal').modal('show'); + Logging.logMessage("Core", "Forms", "Device ID #" + resp.DID + " created!"); + break; + default: + msg = "COVID Create Failed: " + resp.Message + Logging.logMessage("Core", "COVID", msg); + $('.modal-title').text('COVID Classifier Devices'); + $('.modal-body').text(msg); + $('#responsive-modal').modal('show'); + break; + } + }); + }, + Update: function() { + $.post(window.location.href, $("#aml_classifier_update").serialize(), function(resp) { + console.log(resp) + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + var fjson = JSON.stringify(resp.Schema, null, '\t'); + window.parent.$('#schema').html(fjson); + Logging.logMessage("Core", "Forms", "Device Update OK"); + $('.modal-title').text('COVID Classifier Devices'); + $('.modal-body').text(resp.Message); + $('#responsive-modal').modal('show'); + break; + default: + msg = "COVID Update Failed: " + resp.Message + Logging.logMessage("Core", "COVID", msg); + $('.modal-title').text('COVID Classifier Devices'); + $('.modal-body').text(msg); + $('#responsive-modal').modal('show'); + break; + } + }); + }, + deleteData: function() { + $.post(window.location.href, { "deleteData": 1 }, function(resp) { + console.log(resp) + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + $('#dataBlock').empty(); + $('#dataBlock').html("

Please upload your test dataset.

"); + break; + default: + break; + } + }); + }, + prepareUploadForm: function() { + + var upper = document.querySelector('#dataup'), + form = new FormData(), + xhr = new XMLHttpRequest(); + + form.append('uploadAllData', 1); + + upper.addEventListener('change', function(event) { + event.preventDefault(); + + var files = this.files; + for (var i = 0, n = files.length; i < n; i++) { + var file = files[i]; + + form.append('alldata[]', file, file.name); + + xhr.onload = function() { + if (xhr.status === 200) { + console.log(xhr.response) + var resp = jQuery.parseJSON(xhr.response); + if (resp.Response === "OK") { + $('#dataBlock').empty(); + $('#dataBlock').html(resp.Data); + $('.modal-title').text('Data Upload OK'); + $('.modal-body').text(resp.Message); + $('#responsive-modal').modal('show'); + COVID.setOpacity(); + Logging.logMessage("Core", "Forms", resp.Message); + } else { + Logging.logMessage("Core", "Forms", resp.Message); + $('.modal-title').text('Data Upload Failed'); + $('.modal-body').text(resp.Message); + $('#responsive-modal').modal('show'); + } + } + } + + xhr.open('POST', ''); + xhr.send(form); + } + }); + }, + setOpacity: function() { + $('.classify').css("opacity", "1.0"); + $('.classify').hover(function() { + $(this).stop().animate({ opacity: 0.2 }, "fast"); + }, + function() { + $(this).stop().animate({ opacity: 1.0 }, "fast"); + }); + }, + classify: function(im) { + + $('#imageView').html(""); + $("#imName").text(im); + var classification = ''; + $("#imClass").html("Diagnosis: WAITING FOR RESPONSE"); + $("#imResult").html("Result: WAITING FOR RESPONSE"); + $.post(window.location.href, { "classifyData": 1, "im": im }, function(resp) { + console.log(resp) + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + if (im.indexOf("Non-Covid") >= 0 && resp.Diagnosis == "Negative") { + classification = "True Negative"; + } else if (im.indexOf("Non-Covid") >= 0 && resp.Diagnosis == "Positive") { + classification = "False Positive"; + } else if (im.indexOf("Non-Covid") < 0 && resp.Diagnosis == "Positive") { + classification = "True Positive"; + } else if (im.indexOf("Non-Covid") < 0 && resp.Diagnosis == "Negative") { + classification = "False Negative"; + } + $("#imClass").html("Diagnosis: " + resp.Diagnosis); + if (resp.Confidence) { + $("#imConf").html("Confidence: " + resp.Confidence); + } else { + $("#imConf").hide(); + } + $("#imResult").html("Result: " + classification); + break; + default: + break; + } + }); + + } +}; +$(document).ready(function() { + + $('#aml_classifier').validator().on('submit', function(e) { + if (!e.isDefaultPrevented()) { + e.preventDefault(); + COVID.Create(); + } + }); + + $('#aml_classifier_update').validator().on('submit', function(e) { + if (!e.isDefaultPrevented()) { + e.preventDefault(); + COVID.Update(); + } + }); + + $("#GeniSysAI").on("click", "#uploadData", function(e) { + e.preventDefault(); + $('#dataup').trigger('click'); + }); + + $("#GeniSysAI").on("click", "#deleteData", function(e) { + e.preventDefault(); + COVID.deleteData(); + }); + + $("#GeniSysAI").on("click", ".classify", function(e) { + e.preventDefault(); + COVID.classify($(this).attr("id")); + }); + +}); \ No newline at end of file diff --git a/Root/var/www/html/AI/COVID/Classes/COVID.php b/Root/var/www/html/AI/COVID/Classes/COVID.php new file mode 100644 index 0000000..4bd0ea4 --- /dev/null +++ b/Root/var/www/html/AI/COVID/Classes/COVID.php @@ -0,0 +1,1687 @@ +_GeniSys = $_GeniSys; + $this->bcc = $this->getBlockchainConf(); + $this->web3 = $this->blockchainConnection(); + $this->contract = new Contract($this->web3->provider, $this->bcc["abi"]); + $this->icontract = new Contract($this->web3->provider, $this->bcc["iabi"]); + $this->checkBlockchainPermissions(); + endif; + $this->cb = $this->getContextBrokerConf(); + } + + public function setClassifierConfs() + { + $TId = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_NUMBER_INT); + $Device = $this->getDevice($TId); + + $this->dataDir = "Data/" . $Device["context"]["Data"]["dataset"]["folder"] . "/"; + $this->dataDirFull = "/fserver/var/www/html/AI/COVID/"; + $this->dataFiles = $this->dataDir . "*.{JPG,jpg,png,PNG,gif,GIF,tiff,tiff}"; + $this->allowedFiles = ["jpg","JPG","png","PNG","gif","GIF","tiff","tiff"]; + $this->api = $this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"])."/AI/COVID/" . $Device["context"]["Data"]["proxy"]["endpoint"] . "/Inference"; + } + + public function getContextBrokerConf() + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM contextbroker + "); + $pdoQuery->execute(); + $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + return $response; + } + + private function createContextHeaders() + { + $basicAuth = $_SESSION["GeniSysAI"]["User"] . ":" . $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"]); + $basicAuth = base64_encode($basicAuth); + + return [ + "Content-Type: application/json", + 'Authorization: Basic '. $basicAuth + ]; + } + + private function contextBrokerRequest($method, $endpoint, $headers, $json) + { + $path = $this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/" . $this->cb["url"] . "/" . $endpoint; + + if($method == "GET"): + $ch = curl_init(); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_URL, $path); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "POST"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "PATCH"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + endif; + + return $body; + } + + public function getBlockchainConf() + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT blockchain.*, + contracts.contract, + contracts.abi, + icontracts.contract as icontract, + icontracts.abi as iabi + FROM blockchain blockchain + INNER JOIN contracts contracts + ON contracts.id = blockchain.dc + INNER JOIN contracts icontracts + ON icontracts.id = blockchain.ic + "); + $pdoQuery->execute(); + $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + return $response; + } + + private function blockchainConnection() + { + if(isSet($_SESSION["GeniSysAI"]["Active"])): + $web3 = new Web3($this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/Blockchain/API/", 30, $_SESSION["GeniSysAI"]["User"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"])); + return $web3; + endif; + } + + private function checkBlockchainPermissions() + { + $allowed = ""; + $errr = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->call("identifierAllowed", "User", $_SESSION["GeniSysAI"]["Identifier"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$allowed, &$errr) { + if ($err !== null) { + $allowed = "FAILED"; + $errr = $err; + return; + } + $allowed = $resp; + }); + if(!$allowed): + header('Location: /Logout'); + endif; + } + + private function unlockBlockchainAccount() + { + $response = ""; + $personal = $this->web3->personal; + $personal->unlockAccount($_SESSION["GeniSysAI"]["BC"]["BCUser"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["BC"]["BCPass"]), function ($err, $unlocked) use (&$response) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + if ($unlocked) { + $response = "OK"; + } else { + $response = "FAILED"; + } + }); + + return $response; + } + + private function lockBlockchainAccount() + { + $response = ""; + $personal = $this->web3->personal; + $personal->lockAccount($_SESSION["GeniSysAI"]["BC"]["BCUser"], function ($err, $unlocked) use (&$response) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + if ($unlocked) { + $response = "OK"; + } else { + $response = "FAILED"; + } + }); + + return $response; + } + + private function createBlockchainUser($pass) + { + $newAccount = ""; + $personal = $this->web3->personal; + $personal->newAccount($pass, function ($err, $account) use (&$newAccount) { + if ($err !== null) { + $newAccount = "FAILED!"; + return; + } + $newAccount = $account; + }); + + return $newAccount; + } + + private function getBlockchainBalance() + { + $nbalance = ""; + $this->web3->eth->getBalance($_SESSION["GeniSysAI"]["BC"]["BCUser"], function ($err, $balance) use (&$nbalance) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + $nbalance = $balance->toString(); + }); + + return Utils::fromWei($nbalance, 'ether')[0]; + } + + private function addAmqpUser($username, $key) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpu ( + `username`, + `pw` + ) VALUES ( + :username, + :pw + ) + "); + $query->execute([ + ':username' => $username, + ':pw' => $this->_GeniSys->_helpers->oEncrypt($key) + ]); + $amid = $this->_GeniSys->_secCon->lastInsertId(); + return $amid; + } + + private function addAmqpUserVh($uid, $vhost) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvh ( + `uid`, + `vhost` + ) VALUES ( + :uid, + :vhost + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost + ]); + } + + private function addAmqpVhPerm($uid, $vhost, $rtype, $rname, $permission) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvhr ( + `uid`, + `vhost`, + `rtype`, + `rname`, + `permission` + ) VALUES ( + :uid, + :vhost, + :rtype, + :rname, + :permission + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost, + ':rtype' => $rtype, + ':rname' => $rname, + ':permission' => $permission + ]); + } + + private function addAmqpVhTopic($uid, $vhost, $rtype, $rname, $permission, $rkey) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvhrt ( + `uid`, + `vhost`, + `rtype`, + `rname`, + `permission`, + `rkey` + ) VALUES ( + :uid, + :vhost, + :rtype, + :rname, + :permission, + :rkey + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost, + ':rtype' => $rtype, + ':rname' => $rname, + ':permission' => $permission, + ':rkey' => $rkey + ]); + } + + private function storeBlockchainTransaction($action, $hash, $device = 0, $application = 0) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + INSERT INTO transactions ( + `uid`, + `did`, + `aid`, + `action`, + `hash`, + `time` + ) VALUES ( + :uid, + :did, + :aid, + :action, + :hash, + :time + ) + "); + $pdoQuery->execute([ + ":uid" => $_SESSION["GeniSysAI"]["Uid"], + ":did" => $device, + ":aid" => $application, + ":action" => $action, + ':hash' => $this->_GeniSys->_helpers->oEncrypt($hash), + ":time" => time() + ]); + $txid = $this->_GeniSys->_secCon->lastInsertId(); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + return $txid; + } + + private function storeUserHistory($action, $hash, $location = 0, $zone = 0, $device = 0, $sensor = 0, $application = 0) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + INSERT INTO history ( + `uid`, + `tlid`, + `tzid`, + `tdid`, + `tsid`, + `taid`, + `action`, + `hash`, + `time` + ) VALUES ( + :uid, + :tlid, + :tzid, + :tdid, + :tsid, + :taid, + :action, + :hash, + :time + ) + "); + $pdoQuery->execute([ + ":uid" => $_SESSION["GeniSysAI"]["Uid"], + ":tlid" => $location, + ":tzid" => $zone, + ":tdid" => $device, + ":tsid" => $sensor, + ":taid" => $application, + ":action" => $action, + ":hash" => $hash, + ":time" => time() + ]); + $txid = $this->_GeniSys->_secCon->lastInsertId(); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + return $txid; + } + + public function checkLocation($lid) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT id + FROM mqttl + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $lid + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($location["id"]): + return True; + else: + return False; + endif; + } + + public function getLocation($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttl + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $location["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $location["pub"] . "?type=Location" . $attrs, $this->createContextHeaders(), []), true); + return $location; + } + + public function checkZone($zid) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT id + FROM mqttlz + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $zid + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($location["id"]): + return True; + else: + return False; + endif; + } + + public function getZone($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttlz + WHERE id = :id + ORDER BY id DESC + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $zone=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $zone["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $zone["pub"] . "?type=Zone" . $attrs, $this->createContextHeaders(), []), true); + return $zone; + } + + public function getDevices($limit = 0) + { + $limiter = ""; + if($limit != 0): + $limiter = "&limit=" . $limit; + endif; + + $devices = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "?type=Device&category=COVIDClassifier".$limiter, $this->createContextHeaders(), []), true); + return $devices; + } + + public function getDevice($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttld + WHERE id = :id + ORDER BY id DESC + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $device=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $device["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $device["apub"] . "?type=Device" . $attrs, $this->createContextHeaders(), []), true); + return $device; + } + + public function getThing($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM things + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $thing=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $thing["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $thing["pub"] . "?type=Thing" . $attrs, $this->createContextHeaders(), []), true); + return $thing; + } + + public function getModel($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM models + WHERE id = :id + ORDER BY id DESC + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $model=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $device["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $model["pub"] . "?type=Model" . $attrs, $this->createContextHeaders(), []), true); + return $device; + } + + public function createDevice() + { + if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Location ID is required" + ]; + endif; + + if(!$this->checkLocation(filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Zone ID is required" + ]; + endif; + + if(!$this->checkZone(filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay zone does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Category is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ctype", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Model type is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device model is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system version is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Coordinates entity is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IP is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "MAC is required" + ]; + endif; + + if(!count($_POST["protocols"])): + return [ + "Response"=> "Failed", + "Message" => "At least one M2M protocol is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetUsed", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset used is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetLink", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset link is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetAuthor", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset author is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset folder is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "relatedPaper", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Related paper is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Device server port is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "endpoint", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Proxy endpoint is required" + ]; + endif; + + $unlocked = $this->unlockBlockchainAccount(); + + if($unlocked == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Unlocking HIAS Blockhain Account Failed!" + ]; + endif; + + $mqttUser = $this->_GeniSys->_helpers->generate_uuid(); + $mqttPass = $this->_GeniSys->_helpers->password(); + $mqttHash = create_hash($mqttPass); + + $pubKey = $this->_GeniSys->_helpers->generate_uuid(); + $privKey = $this->_GeniSys->_helpers->generateKey(32); + $privKeyHash = $this->_GeniSys->_helpers->createPasswordHash($privKey); + + $amqppubKey = $this->_GeniSys->_helpers->generate_uuid(); + $amqpprvKey = $this->_GeniSys->_helpers->generateKey(32); + $amqpKeyHash = $this->_GeniSys->_helpers->createPasswordHash($amqpprvKey); + + $bcPass = $this->_GeniSys->_helpers->password(); + + $lid = filter_input(INPUT_POST, 'lid', FILTER_SANITIZE_NUMBER_INT); + $location = $this->getLocation($lid); + + $zid = filter_input(INPUT_POST, 'zid', FILTER_SANITIZE_NUMBER_INT); + $zone = $this->getZone($zid); + + $ip = filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING); + $mac = filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING); + $bluetooth = filter_input(INPUT_POST, "bluetooth", FILTER_SANITIZE_STRING); + $name = filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING); + $coords = explode(",", filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)); + + mkdir("Data/" . filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING), 0777, true); + + $protocols = []; + foreach($_POST["protocols"] AS $key => $value): + $protocols[] = $value; + endforeach; + + $models = []; + if(isSet($_POST["ai"])): + foreach($_POST["ai"] AS $key => $value): + $model = $this->getModel($value)["context"]["Data"]; + $mname = $model["name"]["value"]; + unset($model["id"]); + unset($model["type"]); + unset($model["mid"]); + unset($model["name"]); + unset($model["description"]); + unset($model["network"]); + unset($model["language"]); + unset($model["framework"]); + unset($model["toolkit"]); + unset($model["dateCreated"]); + unset($model["dateModified"]); + $models[$mname] = $model; + endforeach; + endif; + + $sensors = []; + if(isSet($_POST["sensors"])): + foreach($_POST["sensors"] AS $key => $value): + $sensor = $this->getThing($value)["context"]["Data"]; + unset($sensor["id"]); + unset($sensor["type"]); + unset($sensor["category"]); + unset($sensor["description"]); + unset($sensor["thing"]); + unset($sensor["properties"]["image"]); + unset($sensor["dateCreated"]); + unset($sensor["dateModified"]); + $sensors[] = $sensor; + endforeach; + endif; + + $actuators = []; + if(isSet($_POST["actuators"])): + foreach($_POST["actuators"] AS $key => $value): + $actuator = $this->getThing($value)["context"]["Data"]; + unset($actuator["id"]); + unset($actuator["type"]); + unset($actuator["category"]); + unset($actuator["description"]); + unset($actuator["thing"]); + unset($actuator["properties"]["image"]); + unset($actuator["dateCreated"]); + unset($actuator["dateModeified"]); + $actuators[] = $actuator; + endforeach; + endif; + + $newBcUser = $this->createBlockchainUser($bcPass); + + if($newBcUser == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Creating New HIAS Blockhain Account Failed!" + ]; + endif; + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttld ( + `apub` + ) VALUES ( + :apub + ) + "); + $query->execute([ + ':apub' => $pubKey + ]); + $did = $this->_GeniSys->_secCon->lastInsertId(); + + $data = [ + "id" => $pubKey, + "type" => "Device", + "category" => [ + "value" => [filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)] + ], + "name" => [ + "value" => $name + ], + "description" => [ + "value" => filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING) + ], + "lid" => [ + "value" => $lid, + "entity" => $location["context"]["Data"]["id"] + ], + "zid" => [ + "value" => $zid, + "entity" => $zone["context"]["Data"]["id"] + ], + "did" => [ + "value" => $did + ], + "location" => [ + "type" => "geo:json", + "value" => [ + "type" => "Point", + "coordinates" => [floatval($coords[0]), floatval($coords[1])] + ] + ], + "agent" => [ + "url" => filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING) + ], + "paper" => [ + "title" => filter_input(INPUT_POST, "relatedPaper", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "relatedPaperAuthor", FILTER_SANITIZE_STRING), + "doi" => filter_input(INPUT_POST, "relatedPaperDOI", FILTER_SANITIZE_STRING), + "link" => filter_input(INPUT_POST, "relatedPaperLink", FILTER_SANITIZE_STRING) + ], + "dataset" => [ + "name" => filter_input(INPUT_POST, "datasetUsed", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "datasetAuthor", FILTER_SANITIZE_STRING), + "url" => filter_input(INPUT_POST, "datasetLink", FILTER_SANITIZE_STRING), + "folder" => filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING) + ], + "device" => [ + "type" => filter_input(INPUT_POST, "ctype", FILTER_SANITIZE_STRING), + "name" => filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING), + "model" => filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING) + ], + "proxy" => [ + "endpoint" => filter_input(INPUT_POST, "endpoint", FILTER_SANITIZE_STRING) + ], + "stream" => [ + "port" => filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING), + "file" => "" + ], + "socket" => [ + "port" => "" + ], + "os" => [ + "name" => filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING), + "version" => filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING) + ], + "protocols" => $protocols, + "status" => [ + "value" => "OFFLINE", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "keys" => [ + "public" => $pubKey, + "private" => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "blockchain" => [ + "address" => $newBcUser, + "password" => $this->_GeniSys->_helpers->oEncrypt($bcPass) + ], + "mqtt" => [ + "username" => $this->_GeniSys->_helpers->oEncrypt($mqttUser), + "password" => $this->_GeniSys->_helpers->oEncrypt($mqttPass), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "coap" => [ + "username" => "", + "password" => "" + ], + "amqp" => [ + "username" => $this->_GeniSys->_helpers->oEncrypt($amqppubKey), + "password" => $this->_GeniSys->_helpers->oEncrypt($amqpprvKey), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "batteryLevel" => [ + "value" => 0.00 + ], + "cpuUsage" => [ + "value" => 0.00 + ], + "memoryUsage" => [ + "value" => 0.00 + ], + "hddUsage" => [ + "value" => 0.00 + ], + "temperature" => [ + "value" => 0.00 + ], + "ip" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($ip), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "mac" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($mac), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "bluetooth" => [ + "address" => $bluetooth ? $this->_GeniSys->_helpers->oEncrypt($bluetooth) : "", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "ai" => $models, + "sensors" => $sensors, + "actuators" => $actuators, + "dateCreated" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateFirstUsed" => [ + "type" => "DateTime", + "value" => "" + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + + $response = json_decode($this->contextBrokerRequest("POST", $this->cb["entities_url"] . "?type=Device", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttu ( + `lid`, + `zid`, + `did`, + `uname`, + `pw` + ) VALUES ( + :lid, + :zid, + :did, + :uname, + :pw + ) + "); + $query->execute([ + ':lid' => $lid, + ':zid' => $zid, + ':did' => $did, + ':uname' => $mqttUser, + ':pw' => $mqttHash + ]); + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttua ( + `lid`, + `zid`, + `did`, + `username`, + `topic`, + `rw` + ) VALUES ( + :lid, + :zid, + :did, + :username, + :topic, + :rw + ) + "); + $query->execute(array( + ':lid' => $lid, + ':zid' => $zid, + ':did' => $did, + ':username' => $mqttUser, + ':topic' => $location["context"]["Data"]["id"] . "/Devices/" . $zone["context"]["Data"]["id"] . "/" . $pubKey . "/#", + ':rw' => 4 + )); + + $amid = $this->addAmqpUser($amqppubKey, $amqpKeyHash); + $this->addAmqpUserVh($amid, "iotJumpWay"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "exchange", "Core", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "exchange", "Core", "write"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Life", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Life", "write"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Statuses", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Statuses", "write"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "read", "Life"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "write", "Life"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "read", "Statuses"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "write", "Statuses"); + + $hash = ""; + $msg = ""; + $actionMsg = ""; + $balanceMessage = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("registerDevice", $pubKey, $newBcUser, $lid, $zid, $did, $name, $_SESSION["GeniSysAI"]["Uid"], time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg = " HIAS Blockchain registerDevice failed!\n" . $msg; + else: + $txid = $this->storeBlockchainTransaction("Register Device", $hash, $did); + $this->storeUserHistory("Register Device", $txid, $lid, $zid, $did); + $balance = $this->getBlockchainBalance(); + $actionMsg = " HIAS Blockchain registerDevice OK!\n"; + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; + endif; + + $this->icontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["icontract"]))->send("registerAuthorized", $newBcUser, ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg .= " HIAS Blockchain registerAuthorized failed! " . $msg; + else: + $txid = $this->storeBlockchainTransaction("iotJumpWay Register Authorized", $hash, $did); + $this->storeUserHistory("Register Authorized", $txid, $lid, $zid, $did); + $balance = $this->getBlockchainBalance(); + $actionMsg .= " HIAS Blockchain registerAuthorized OK!\n"; + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; + endif; + + return [ + "Response"=> "OK", + "Message" => "Device created!" . $actionMsg . $balanceMessage, + "LID" => filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT), + "ZID" => filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT), + "DID" => $did, + "MU" => $mqttUser, + "MP" => $mqttPass, + "BU" => $newBcUser, + "BP" => $bcPass, + "AppID" => $pubKey, + "AppKey" => $privKey + ]; + else: + return [ + "Response"=> "FAILED", + "Message" => "Device creating failed" + ]; + endif; + } + + public function updateDevice() + { + if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Location ID is required" + ]; + endif; + + if(!$this->checkLocation(filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Zone ID is required" + ]; + endif; + + if(!$this->checkZone(filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay zone does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Category is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ctype", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Model type is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device model is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system version is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Coordinates entity is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IP is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "MAC is required" + ]; + endif; + + if(!count($_POST["protocols"])): + return [ + "Response"=> "Failed", + "Message" => "At least one M2M protocol is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetUsed", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset used is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetLink", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset link is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetAuthor", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset author is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset folder is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "relatedPaper", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Related paper is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Device server port is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "endpoint", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Proxy endpoint is required" + ]; + endif; + + $unlocked = $this->unlockBlockchainAccount(); + + if($unlocked == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Unlocking HIAS Blockhain Account Failed!" + ]; + endif; + + $ip = filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING); + $mac = filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING); + $bluetooth = filter_input(INPUT_POST, "bluetooth", FILTER_SANITIZE_STRING); + $name = filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING); + $status = filter_input(INPUT_POST, "status", FILTER_SANITIZE_STRING); + $coords = explode(",", filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)); + + $did = filter_input(INPUT_GET, "device", FILTER_SANITIZE_NUMBER_INT); + $device = $this->getDevice($did); + + $lid = filter_input(INPUT_POST, 'lid', FILTER_SANITIZE_NUMBER_INT); + $location = $this->getLocation($lid); + + $zid = filter_input(INPUT_POST, 'zid', FILTER_SANITIZE_NUMBER_INT); + $zone = $this->getZone($zid); + + $protocols = []; + foreach($_POST["protocols"] AS $key => $value): + $protocols[] = $value; + endforeach; + + $models = []; + if(isSet($_POST["ai"])): + foreach($_POST["ai"] AS $key => $value): + $model = $this->getModel($value)["context"]["Data"]; + $mname = $model["name"]["value"]; + unset($model["id"]); + unset($model["type"]); + unset($model["mid"]); + unset($model["name"]); + unset($model["description"]); + unset($model["network"]); + unset($model["language"]); + unset($model["framework"]); + unset($model["toolkit"]); + unset($model["dateCreated"]); + unset($model["dateModified"]); + $models[$mname] = $model; + endforeach; + endif; + + $sensors = []; + if(isSet($_POST["sensors"])): + foreach($_POST["sensors"] AS $key => $value): + $sensor = $this->getThing($value)["context"]["Data"]; + unset($sensor["id"]); + unset($sensor["type"]); + unset($sensor["category"]); + unset($sensor["description"]); + unset($sensor["thing"]); + unset($sensor["properties"]["image"]); + unset($sensor["dateCreated"]); + unset($sensor["dateModified"]); + $sensors[] = $sensor; + endforeach; + endif; + + $actuators = []; + if(isSet($_POST["actuators"])): + foreach($_POST["actuators"] AS $key => $value): + $actuator = $this->getThing($value)["context"]["Data"]; + unset($actuator["id"]); + unset($actuator["type"]); + unset($actuator["category"]); + unset($actuator["description"]); + unset($actuator["thing"]); + unset($actuator["properties"]["image"]); + unset($actuator["dateCreated"]); + unset($actuator["dateModeified"]); + $actuators[] = $actuator; + endforeach; + endif; + + if($device["context"]["Data"]["lid"]["value"] != $lid): + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttu + SET lid = :lid + WHERE did = :did + "); + $query->execute([ + ':lid' => $lid, + ':did' => $did + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttua + SET lid = :lid + WHERE did = :did + "); + $query->execute([ + ':lid' => $lid, + ':did' => $did + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttua + SET topic = :topicN + WHERE did = :did + & topic = :topic + "); + $query->execute([ + ':topicN' => $device["context"]["Data"]["lid"]["entity"] . "/ " . $device["context"]["Data"]["zid"]["entity"] . "/Devices/" . $device["context"]["Data"]["id"] . "/#", + ':did' => $did, + ':topic' => $location["context"]["Data"]["id"] . "/Devices/" . $zone["context"]["Data"]["id"] . "/Devices/" . $device["context"]["Data"]["id"] . "/#" + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + endif; + + $data = [ + "category" => [ + "value" => [filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)] + ], + "name" => [ + "value" => $name + ], + "description" => [ + "value" => filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING) + ], + "lid" => [ + "value" => $lid, + "entity" => $location["context"]["Data"]["id"] + ], + "zid" => [ + "value" => $zid, + "entity" => $zone["context"]["Data"]["id"] + ], + "location" => [ + "type" => "geo:json", + "value" => [ + "type" => "Point", + "coordinates" => [floatval($coords[0]), floatval($coords[1])] + ] + ], + "agent" => [ + "url" => filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING) + ], + "paper" => [ + "title" => filter_input(INPUT_POST, "relatedPaper", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "relatedPaperAuthor", FILTER_SANITIZE_STRING), + "doi" => filter_input(INPUT_POST, "relatedPaperDOI", FILTER_SANITIZE_STRING), + "link" => filter_input(INPUT_POST, "relatedPaperLink", FILTER_SANITIZE_STRING) + ], + "dataset" => [ + "name" => filter_input(INPUT_POST, "datasetUsed", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "datasetAuthor", FILTER_SANITIZE_STRING), + "url" => filter_input(INPUT_POST, "datasetLink", FILTER_SANITIZE_STRING), + "folder" => filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING) + ], + "device" => [ + "type" => filter_input(INPUT_POST, "ctype", FILTER_SANITIZE_STRING), + "name" => filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING), + "model" => filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING) + ], + "proxy" => [ + "endpoint" => filter_input(INPUT_POST, "endpoint", FILTER_SANITIZE_STRING) + ], + "stream" => [ + "port" => filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING), + "file" => "" + ], + "socket" => [ + "port" => "" + ], + "os" => [ + "name" => filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING), + "version" => filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING) + ], + "protocols" => $protocols, + "ip" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($ip), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "mac" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($mac), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "bluetooth" => [ + "address" => $bluetooth ? $this->_GeniSys->_helpers->oEncrypt($bluetooth) : "", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "ai" => $models, + "sensors" => $sensors, + "actuators" => $actuators, + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $device["context"]["Data"]["id"] . "/attrs?type=Device", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + + $hash = ""; + $msg = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("updateDevice", $device["context"]["Data"]["id"], "Device", $lid, $zid, $did, $name, $device["context"]["Data"]["status"]["value"], time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + $balance = ""; + $balanceMessage = ""; + $actionMsg = ""; + if($hash == "FAILED"): + $actionMsg = " HIAS Blockchain updateDevice failed! " . $msg; + else: + $txid = $this->storeBlockchainTransaction("Update Device", $hash, $did); + $this->storeUserHistory("Updated Device", $txid, $lid, $zid, $did); + $balance = $this->getBlockchainBalance(); + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; + endif; + + $device = $this->getDevice($did); + + return [ + "Response"=> "OK", + "Message" => "Device updated!" . $actionMsg . $balanceMessage, + "Schema" => $device["context"]["Data"] + ]; + else: + return [ + "Response"=> "Failed", + "Message" => "There was a problem updating this device context data!" + ]; + endif; + } + + public function deleteData() + { + $images = glob($this->dataFiles); + foreach( $images as $image ): + unlink($image); + endforeach; + + return [ + "Response" => "OK", + "Message" => "Deleted Acute Lymphoblastic Leukemia Image Database for Image Processing Dataset" + ]; + + } + + public function uploadData() + { + $dataCells = ''; + if(is_array($_FILES) && !empty($_FILES['alldata'])): + foreach($_FILES['alldata']['name'] as $key => $filename): + $file_name = explode(".", $filename); + if(in_array($file_name[1], $this->allowedFiles)): + $sourcePath = $_FILES["alldata"]["tmp_name"][$key]; + $targetPath = $this->dataDir . $filename; + if(!move_uploaded_file($sourcePath, $targetPath)): + return [ + "Response" => "FAILED", + "Message" => "Upload failed " . $targetPath + ]; + endif; + else: + return [ + "Response" => "FAILED", + "Message" => "Please upload jpg files" + ]; + endif; + endforeach; + + $images = glob($this->dataFiles, GLOB_BRACE); + $count = 1; + foreach($images as $image): + $dataCells .= "
"; + if($count%6 == 0): + $dataCells .= "
"; + endif; + $count++; + endforeach; + + else: + return [ + "Response" => "FAILED", + "Message" => "You must upload some images (jpg)" + ]; + endif; + + return [ + "Response" => "OK", + "Message" => "Data upload OK!", + "Data" => $dataCells + ]; + + } + + public function classifyData() + { + $file = $this->dataDirFull . filter_input(INPUT_POST, "im", FILTER_SANITIZE_STRING); + $mime = mime_content_type($file); + $info = pathinfo($file); + $name = $info['basename']; + $toSend = new CURLFile($file, $mime, $name); + + $headers = [ + 'Authorization: Basic '. base64_encode($_SESSION["GeniSysAI"]["User"] . ":" . $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"])) + ]; + + $ch = curl_init($this->api); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_POSTFIELDS, [ + 'file'=> $toSend, + ]); + + $resp = curl_exec($ch); + + return json_decode($resp, true); + + } + + } + + $COVID = new COVID($_GeniSys); + + if(filter_input(INPUT_POST, "create_aml_classifier", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($COVID->createDevice())); + endif; + + if(filter_input(INPUT_POST, "update_aml_classifier", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($COVID->updateDevice())); + endif; + + if(filter_input(INPUT_POST, "reset_mqtt", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($COVID->resetMqtt())); + endif; + + if(filter_input(INPUT_POST, "reset_key", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($COVID->resetDvcKey())); + endif; + + if(filter_input(INPUT_POST, "get_tlife", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($COVID->getLife())); + endif; + + if(filter_input(INPUT_POST, "deleteData", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($COVID->deleteData())); + endif; + + if(filter_input(INPUT_POST, "uploadAllData", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($COVID->uploadData())); + endif; + + if(filter_input(INPUT_POST, "classifyData", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($COVID->classifyData())); + endif; diff --git a/Root/var/www/html/AI/COVID/Classify.php b/Root/var/www/html/AI/COVID/Classify.php new file mode 100644 index 0000000..3306924 --- /dev/null +++ b/Root/var/www/html/AI/COVID/Classify.php @@ -0,0 +1,202 @@ + "AI", + "SubPageID" => "AICOVID" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/COVID/Classes/COVID.php'; + +$_GeniSysAi->checkSession(); + +$Locations = $iotJumpWay->getLocations(); +$Zones = $iotJumpWay->getZones(); +$Devices = $COVID->getDevices(); + +$TId = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_NUMBER_INT); +$Device = $iotJumpWay->getDevice($TId); + +list($dev1On, $dev1Off) = $iotJumpWay->getStatusShow($Device["context"]["Data"]["status"]["value"]); + +$COVID->setClassifierConfs(); + +?> + + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
COVID Classifier #
+
+ +
+
+
+
+ + + +
+ + dataFiles, GLOB_BRACE); + $count = 1; + if(count($images)): + foreach( $images as $image ): + echo "
"; + if($count%6 == 0): + echo"
"; + endif; + $count++; + endforeach; + else: + echo "

Please upload your test dataset.

"; + endif; + ?> + +
+
+
+
+
+
+
+
+
+
Online Offline
+
+ +
+  %    +  %    +  %    +  %    +  °C +
+
+
+
+
+
+
+
+
Diagnosis Results
+
+
+
+
+
+
+
+ +
+
+

+
+
+ +
+ +
+
+
+
+
+ +
+ + + +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/AI/COVID/Create.php b/Root/var/www/html/AI/COVID/Create.php new file mode 100644 index 0000000..750bfae --- /dev/null +++ b/Root/var/www/html/AI/COVID/Create.php @@ -0,0 +1,422 @@ + "AI", + "SubPageID" => "AICOVID" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/COVID/Classes/COVID.php'; +include dirname(__FILE__) . '/../../AI/Classes/AI.php'; + +$_GeniSysAi->checkSession(); + +$Zones = $iotJumpWay->getZones(); +$Devices = $iotJumpWay->getDevices(); + +?> + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
Create COVID Classifier Device
+
+
+
+
+
+
+
+
+
+
+
+ + + Name of device +
+
+ + + Description of device +
+
+ + + Device category +
+
+ + + Type of COVID model +
+
+ + + Device IoT Agent +
+
+ + + Name of hardware device +
+
+ + + Name of hardware manufacturer +
+
+ + + Hardware model +
+
+ + + Operating system name +
+
+ + + Operating system manufacturer +
+
+ + + Operating system version +
+
+ + + Supported Communication Protocols +
+
+ +
+
+ Device Sensors + +
+
+ +
+
+ Device Actuators + +
+
+ + + Device AI Models +
+
+ + +
+
+
+
+ + + Dataset used to train and test model +
+
+ + + Dataset link +
+
+ + + Dataset author +
+
+ + + Dataset folder to be created to hold the test data +
+
+ + + Related research paper +
+
+ + + Related research paper author +
+
+ + + Related research paper DOI +
+
+ + + Related research paper link +
+
+ + + Location of device +
+
+ + + Zone of device +
+
+ + + iotJumpWay Device coordinates +
+
+ + + IP of device +
+
+ + + MAC of device +
+
+ + + Bluetooth address of device +
+
+ + + Server port of COVID classifier device +
+
+ + + Endpoint name of NGINX reverse proxy +
+
+
+
+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + + + diff --git a/Root/var/www/html/Detection/COVID-19/CNN/Data/__init__.py b/Root/var/www/html/AI/COVID/Data/.keep similarity index 100% rename from Root/var/www/html/Detection/COVID-19/CNN/Data/__init__.py rename to Root/var/www/html/AI/COVID/Data/.keep diff --git a/Root/var/www/html/AI/COVID/Device.php b/Root/var/www/html/AI/COVID/Device.php new file mode 100644 index 0000000..6b2f1e1 --- /dev/null +++ b/Root/var/www/html/AI/COVID/Device.php @@ -0,0 +1,955 @@ + "AI", + "SubPageID" => "AICOVID" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/COVID/Classes/COVID.php'; +include dirname(__FILE__) . '/../../AI/Classes/AI.php'; + +$_GeniSysAi->checkSession(); + +$Locations = $iotJumpWay->getLocations(); +$Zones = $iotJumpWay->getZones(); +$Devices = $COVID->getDevices(); + +$TId = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_NUMBER_INT); +$Device = $iotJumpWay->getDevice($TId); + +list($dev1On, $dev1Off) = $iotJumpWay->getStatusShow($Device["context"]["Data"]["status"]["value"]); + +?> + + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
COVID Classifier Device #
+
+
+
+
+
+
+
+
+
+
+
+ + "> + Name of device +
+
+ + "> + Description of device +
+
+ + + Device category +
+
+ + + Type of COVID model +
+
+ + + Device IoT Agent +
+
+ + "> + Name of hardware device +
+
+ + "> + Name of hardware manufacturer +
+
+ + "> + Hardware model +
+
+ + "> + Operating system name +
+
+ + "> + Operating system manufacturer +
+
+ + "> + Operating system version +
+
+ + + Supported Communication Protocols +
+
+ +
+
+ $value): + ?> + +
+
+ + " required> +
+
+ +
+
+ + +
+ Device Sensors + +
+
+ +
+
+ $value): + ?> + +
+
+ + " required> +
+
+ +
+
+ + +
+ Device Actuators + +
+
+ + + Device AI Models +
+
+ + +
+
+
+
+ + "> + Dataset used to train and test model +
+
+ + "> + Dataset link +
+
+ + "> + Dataset author +
+
+ + "> + Dataset folder on HIAS +
+
+ + "> + Related research paper +
+
+ + "> + Related research paper author +
+
+ + "> + Related research paper DOI +
+
+ + "> + Related research paper link +
+
+ + + Location of device +
+
+ + + Zone of device +
+
+ + , "> + iotJumpWay Device coordinates +
+
+ + _helpers->oDecrypt($Device["context"]["Data"]["ip"]["value"]) : ""; ?>"> + IP of device +
+
+ + _helpers->oDecrypt($Device["context"]["Data"]["mac"]["value"]) : ""; ?>"> + MAC of device +
+
+ + _helpers->oDecrypt($Device["context"]["Data"]["bluetooth"]["address"]) : ""; ?>"> + Bluetooth address of device +
+
+ + "> + Port of COVID stream +
+
+ + "> + Endpoint name of NGINX reverse proxy +
+
+ +

+
+
+ +

+
+
+ +

+
+
+
+
+
+
+
+

+
+
+
+
Device Schema
+
+
+
+
+
+
+
+ "; ?> "; ?> +
+
+
+

+
+ +
+
+
+
+ + + + + + + + + + + + retrieveDeviceHistory($Device["context"]["Data"]["did"]["value"], 5); + if(count($history)): + foreach($history as $key => $value): + if($value["uid"]): + $user = $_GeniSysAi->getUser($value["uid"]); + $userDetails = "User ID #" . $value["uid"] . " (" . $user["name"] . ") "; + endif; + ?> + + + + + + + + + + + +
IDActionReceiptTime
# + + + /Zones//Devices//Transaction/"># + + NA + + + + +
+
+
+
+
+

+
+ +
+
+
+
+ + + + + + + + + + + + retrieveDeviceTransactions($Device["context"]["Data"]["did"]["value"], 5); + if(count($transactions)): + foreach($transactions as $key => $value): + if($value["uid"]): + $user = $_GeniSysAi->getUser($value["uid"]); + $userDetails = "User ID #" . $value["uid"] . " (" . $user["name"] . ") "; + endif; + ?> + + + + + + + + + + + +
IDActionReceiptTime
#/Zones//Devices//Transaction/">#
+
+
+
+
+

+
+
+
+
Device iotJumpWay Statuses
+
+ +
+
+
+
+
+
+ + + + + + + + + + + retrieveDeviceStatuses($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + ?> + + + + + + + + + + +
IDStatusTime
#_id;?>Status;?>Time;?>
+
+
+
+
+

+
+
+
+
Device iotJumpWay Life
+
+ +
+
+
+
+
+
+ + + + + + + + + + + retrieveDeviceLife($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + ?> + + + + + + + + + + +
IDDetailsTime
#_id;?> + CPU: Data->CPU;?>%
+ Memory: Data->Memory;?>%
+ Diskspace: Data->Diskspace;?>%
+ Temperature: Data->Temperature;?>°C
+ Latitude: Data->Latitude;?>
+ Longitude: Data->Longitude;?>
+
Time;?>
+
+
+
+
+

+
+
+
+
Device iotJumpWay Sensors
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + retrieveDeviceSensors($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + $location = $iotJumpWay->getLocation($value->Location); + ?> + + + + + + + + + + + + +
IDTypeSensorValueMessageTime
#_id;?>Type;?>Sensor;?> + Sensor == "Facial API" || $value->Sensor == "Foscam Camera" || $value->Sensor == "USB Camera") && is_array($value->Value)): + foreach($value->Value AS $key => $val): + echo $val[0] == 0 ? "Identification: Intruder
" :"Identification: User #" . $val[0] . "
"; + echo "Distance: " . $val[1] . "
"; + echo "Message: " . $val[2] . "

"; + endforeach; + else: + echo $value->Value; + endif; + ?> + +
Message;?>Time;?>
+
+
+
+
+

+
+
+
+
Device iotJumpWay Commands
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + retrieveDeviceCommands($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + $location = $iotJumpWay->getLocation($value->Location); + ?> + + + + + + + + + + + +
IDDetailsStatusTime
#_id;?>Location: #Location;?> - Status;?>Time;?>
+
+
+
+
+
+
+
+
+
+
+
Online Offline
+
+ +
+  %    +  %    +  %    +  %    +  °C +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+

+

Last Updated:

+
+
+
+
+
+
+
+
+
+
+ +
+

+
+
+
+
+
+
+
+
+ +
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["mqtt"]["username"]); ?>

+
+
+
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["mqtt"]["password"]); ?> +

Last Updated:

+

+
+
+
+
+
+
+
+
+ +
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["amqp"]["username"]) : ""; ?>

+
+
+
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["amqp"]["password"]) : ""; ?> +

Last Updated:

+

+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/AI/COVID/index.php b/Root/var/www/html/AI/COVID/index.php new file mode 100644 index 0000000..839e67b --- /dev/null +++ b/Root/var/www/html/AI/COVID/index.php @@ -0,0 +1,179 @@ + "AI", + "SubPageID" => "AICOVID" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/COVID/Classes/COVID.php'; + +$_GeniSysAi->checkSession(); +$Devices = $COVID->getDevices(); + +?> + + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
COVID-19 (SARS-CoV-2) Models
+
+
+
+
+
+
+ +

The COVID-19 (SARS-CoV-2) Models are a range of computer vision models for detecting COVID-19 (SARS-CoV-2), designed by the Peter Moss COVID-19 AI Research Project team in partnership with Plamenlancaster/Lancaster University/LIRA. The models are designed to be used on constrained devices making them suitable for IoT networks. These models use a variety of programming languages, frameworks and hardware providing. You can download our COVID-19 models from the Peter Moss COVID-19 AI Research Project Project Github repository, instructions for installation are provided in the tutorials. To find out more about the Peter Moss COVID-19 AI Research Project, you can visit the research project homepage on our website. To find out more about the Peter Moss COVID-19 AI Research Project, you can visit the research project homepage on our website.

+
+
+
+
+
+
+

In addition to using COVID-19 (SARS-CoV-2) detection models created by the Peter Moss COVID-19 AI Research Project team, you can develop your own models and connect them up to the HIAS network.

+
+
+
+
+
+
+
COVID-19 (SARS-CoV-2) Devices
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + $value): + ?> + + + + + + + + + + + +
IDDETAILSSTATUSACTION
# + Name:
+ Zone: # +
+
"> + +
+
"> Edit | /Classify"> Classify
+
+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/AI/Classes/AI.js b/Root/var/www/html/AI/Classes/AI.js new file mode 100644 index 0000000..27750af --- /dev/null +++ b/Root/var/www/html/AI/Classes/AI.js @@ -0,0 +1,128 @@ +var AI = { + Create: function() { + $.post(window.location.href, $("#ai_model").serialize(), function(resp) { + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + GeniSys.ResetForm("ai_model"); + $('.modal-title').text('AI Models'); + $('.modal-body').html("HIAS AI Model created!"); + $('#responsive-modal').modal('show'); + Logging.logMessage("Core", "Forms", "HIAS AI Model created!"); + break; + default: + msg = "AI Model Create Failed: " + resp.Message + Logging.logMessage("Core", "AI", msg); + $('.modal-title').text('AI Models'); + $('.modal-body').text(msg); + $('#responsive-modal').modal('show'); + break; + } + }); + }, + Update: function() { + $.post(window.location.href, $("#model_update").serialize(), function(resp) { + console.log(resp) + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + var fjson = JSON.stringify(resp.Schema, null, '\t'); + window.parent.$('#schema').html(fjson); + Logging.logMessage("Core", "AI", resp.Message); + $('.modal-title').text('AI Models'); + $('.modal-body').text(resp.Message); + $('#responsive-modal').modal('show'); + break; + default: + msg = "AI Update Failed: " + resp.Message + Logging.logMessage("Core", "AI", msg); + $('.modal-title').text('AI Models'); + $('.modal-body').text(msg); + $('#responsive-modal').modal('show'); + break; + } + }); + }, +}; +$(document).ready(function() { + + $("#GeniSysAI").on("click", ".removeModelProperty", function(e) { + e.preventDefault(); + $('#model-property-' + $(this).data('id')).fadeOut(300, function() { $(this).remove(); }); + }); + + $("#GeniSysAI").on("click", ".removeModelCommand", function(e) { + e.preventDefault(); + $('#model-command-' + $(this).data('id')).fadeOut(300, function() { $(this).remove(); }); + }); + + $("#GeniSysAI").on("click", ".removeModelState", function(e) { + e.preventDefault(); + $('#model-state-' + $(this).data('id')).fadeOut(300, function() { $(this).remove(); }); + }); + + $("#GeniSysAI").on("click", "#addModelProperty", function(e) { + e.preventDefault(); + $('.modal-title').text('Add Property'); + $('.modal-footer button').text('OK'); + $('#buttonId').button('option', 'label', 'OK'); + $('.modal-body').html("
Property:
"); + $('#responsive-modal').modal('show'); + $('#responsive-modal').on('hide.bs.modal', function() { + if ($("#addPropertyKey").val()) { + var addProperty = '
'; + $("#propertyContent").append(addProperty); + $('.modal-body').html(""); + } + }) + }); + + $("#GeniSysAI").on("click", "#addModelCommand", function(e) { + e.preventDefault(); + $('.modal-title').text('Add Command'); + $('.modal-footer button').text('OK'); + $('#buttonId').button('option', 'label', 'OK'); + $('.modal-body').html("
Command Name:
Commands:
"); + $('#responsive-modal').modal('show'); + $('#responsive-modal').on('hide.bs.modal', function() { + if ($("#addCommandKey").val() && $("#addCommandValue").val()) { + var addCommand = '
' + $("#addCommandKey").val() + '

'; + $("#commandsContent").append(addCommand); + $('.modal-body').html(""); + } + }) + }); + + $("#GeniSysAI").on("click", "#addModelState", function(e) { + e.preventDefault(); + $('.modal-title').text('Add State'); + $('.modal-footer button').text('OK'); + $('#buttonId').button('option', 'label', 'OK'); + $('.modal-body').html("
State Value:
"); + $('#responsive-modal').modal('show'); + $('#responsive-modal').on('hide.bs.modal', function() { + if ($("#addStateValue").val()) { + var key = (parseInt($("#lastState").text()) + 1); + var addState = '
'; + $("#stateContent").append(addState); + $('.modal-body').html(""); + $("#lastState").text(key); + } + }) + }); + + $('#ai_model').validator().on('submit', function(e) { + if (!e.isDefaultPrevented()) { + e.preventDefault(); + AI.Create(); + } + }); + + $('#model_update').validator().on('submit', function(e) { + if (!e.isDefaultPrevented()) { + e.preventDefault(); + AI.Update(); + } + }); + +}); \ No newline at end of file diff --git a/Root/var/www/html/AI/Classes/AI.php b/Root/var/www/html/AI/Classes/AI.php new file mode 100644 index 0000000..6e5b988 --- /dev/null +++ b/Root/var/www/html/AI/Classes/AI.php @@ -0,0 +1,704 @@ +_GeniSys = $_GeniSys; + $this->bcc = $this->getBlockchainConf(); + $this->web3 = $this->blockchainConnection(); + $this->contract = new Contract($this->web3->provider, $this->bcc["abi"]); + $this->icontract = new Contract($this->web3->provider, $this->bcc["iabi"]); + $this->checkBlockchainPermissions(); + endif; + $this->cb = $this->getContextBrokerConf(); + } + + public function getContextBrokerConf() + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM contextbroker + "); + $pdoQuery->execute(); + $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + return $response; + } + + private function createContextHeaders() + { + $basicAuth = $_SESSION["GeniSysAI"]["User"] . ":" . $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"]); + $basicAuth = base64_encode($basicAuth); + + return [ + "Content-Type: application/json", + 'Authorization: Basic '. $basicAuth + ]; + } + + private function contextBrokerRequest($method, $endpoint, $headers, $json) + { + $path = $this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/" . $this->cb["url"] . "/" . $endpoint; + + if($method == "GET"): + $ch = curl_init(); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_URL, $path); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "POST"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "PATCH"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + endif; + + return $body; + } + + public function getBlockchainConf() + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT blockchain.*, + contracts.contract, + contracts.abi, + icontracts.contract as icontract, + icontracts.abi as iabi + FROM blockchain blockchain + INNER JOIN contracts contracts + ON contracts.id = blockchain.dc + INNER JOIN contracts icontracts + ON icontracts.id = blockchain.ic + "); + $pdoQuery->execute(); + $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + return $response; + } + + private function blockchainConnection() + { + if(isSet($_SESSION["GeniSysAI"]["Active"])): + $web3 = new Web3($this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/Blockchain/API/", 30, $_SESSION["GeniSysAI"]["User"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"])); + return $web3; + endif; + } + + private function checkBlockchainPermissions() + { + $allowed = ""; + $errr = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->call("identifierAllowed", "User", $_SESSION["GeniSysAI"]["Identifier"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$allowed, &$errr) { + if ($err !== null) { + $allowed = "FAILED"; + $errr = $err; + return; + } + $allowed = $resp; + }); + if(!$allowed): + header('Location: /Logout'); + endif; + } + + private function unlockBlockchainAccount() + { + $response = ""; + $personal = $this->web3->personal; + $personal->unlockAccount($_SESSION["GeniSysAI"]["BC"]["BCUser"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["BC"]["BCPass"]), function ($err, $unlocked) use (&$response) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + if ($unlocked) { + $response = "OK"; + } else { + $response = "FAILED"; + } + }); + + return $response; + } + + private function lockBlockchainAccount() + { + $response = ""; + $personal = $this->web3->personal; + $personal->lockAccount($_SESSION["GeniSysAI"]["BC"]["BCUser"], function ($err, $unlocked) use (&$response) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + if ($unlocked) { + $response = "OK"; + } else { + $response = "FAILED"; + } + }); + + return $response; + } + + private function createBlockchainUser($pass) + { + $newAccount = ""; + $personal = $this->web3->personal; + $personal->newAccount($pass, function ($err, $account) use (&$newAccount) { + if ($err !== null) { + $newAccount = "FAILED!"; + return; + } + $newAccount = $account; + }); + + return $newAccount; + } + + private function getBlockchainBalance() + { + $nbalance = ""; + $this->web3->eth->getBalance($_SESSION["GeniSysAI"]["BC"]["BCUser"], function ($err, $balance) use (&$nbalance) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + $nbalance = $balance->toString(); + }); + + return Utils::fromWei($nbalance, 'ether')[0]; + } + + private function addAmqpUser($username, $key) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpu ( + `username`, + `pw` + ) VALUES ( + :username, + :pw + ) + "); + $query->execute([ + ':username' => $username, + ':pw' => $this->_GeniSys->_helpers->oEncrypt($key) + ]); + $amid = $this->_GeniSys->_secCon->lastInsertId(); + return $amid; + } + + private function addAmqpUserVh($uid, $vhost) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvh ( + `uid`, + `vhost` + ) VALUES ( + :uid, + :vhost + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost + ]); + } + + private function addAmqpVhPerm($uid, $vhost, $rtype, $rname, $permission) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvhr ( + `uid`, + `vhost`, + `rtype`, + `rname`, + `permission` + ) VALUES ( + :uid, + :vhost, + :rtype, + :rname, + :permission + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost, + ':rtype' => $rtype, + ':rname' => $rname, + ':permission' => $permission + ]); + } + + private function addAmqpVhTopic($uid, $vhost, $rtype, $rname, $permission, $rkey) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvhrt ( + `uid`, + `vhost`, + `rtype`, + `rname`, + `permission`, + `rkey` + ) VALUES ( + :uid, + :vhost, + :rtype, + :rname, + :permission, + :rkey + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost, + ':rtype' => $rtype, + ':rname' => $rname, + ':permission' => $permission, + ':rkey' => $rkey + ]); + } + + private function storeBlockchainTransaction($action, $hash, $device = 0, $application = 0) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + INSERT INTO transactions ( + `uid`, + `did`, + `aid`, + `action`, + `hash`, + `time` + ) VALUES ( + :uid, + :did, + :aid, + :action, + :hash, + :time + ) + "); + $pdoQuery->execute([ + ":uid" => $_SESSION["GeniSysAI"]["Uid"], + ":did" => $device, + ":aid" => $application, + ":action" => $action, + ':hash' => $this->_GeniSys->_helpers->oEncrypt($hash), + ":time" => time() + ]); + $txid = $this->_GeniSys->_secCon->lastInsertId(); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + return $txid; + } + + private function storeUserHistory($action, $hash, $location = 0, $zone = 0, $device = 0, $sensor = 0, $application = 0) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + INSERT INTO history ( + `uid`, + `tlid`, + `tzid`, + `tdid`, + `tsid`, + `taid`, + `action`, + `hash`, + `time` + ) VALUES ( + :uid, + :tlid, + :tzid, + :tdid, + :tsid, + :taid, + :action, + :hash, + :time + ) + "); + $pdoQuery->execute([ + ":uid" => $_SESSION["GeniSysAI"]["Uid"], + ":tlid" => $location, + ":tzid" => $zone, + ":tdid" => $device, + ":tsid" => $sensor, + ":taid" => $application, + ":action" => $action, + ":hash" => $hash, + ":time" => time() + ]); + $txid = $this->_GeniSys->_secCon->lastInsertId(); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + return $txid; + } + + public function getModels($limit = 0) + { + $limiter = ""; + if($limit != 0): + $limiter = "&limit=" . $limit; + endif; + + $devices = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "?type=Model".$limiter, $this->createContextHeaders(), []), true); + return $devices; + } + + public function getModel($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM models + WHERE id = :id + ORDER BY id DESC + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $model=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $device["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $model["pub"] . "?type=Model" . $attrs, $this->createContextHeaders(), []), true); + return $device; + } + + public function createModel() + { + if(!filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Category is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Description is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ntype", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Network type is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ntype", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Network type is required" + ]; + endif; + + $pubKey = $this->_GeniSys->_helpers->generate_uuid(); + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO models ( + `pub` + ) VALUES ( + :pub + ) + "); + $query->execute([ + ':pub' => $pubKey + ]); + $mid = $this->_GeniSys->_secCon->lastInsertId(); + + $properties=[]; + if(isSet($_POST["properties"])): + foreach($_POST["properties"] AS $key => $value): + $properties[$value] = ["value" => ""]; + endforeach; + endif; + + $commands=[]; + if(isSet($_POST["commands"])): + foreach($_POST["commands"] AS $key => $value): + $values = explode(",", $value); + $commands[$key] = $values; + endforeach; + endif; + + $states=[]; + $state=[]; + if(isSet($_POST["states"])): + $states = $_POST["states"]; + $state = [ + "value" => "", + "timestamp" => "" + ]; + endif; + + $data = [ + "id" => $pubKey, + "type" => "Model", + "category" => [ + "value" => [filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)] + ], + "mid" => [ + "value" => $mid + ], + "name" => [ + "value" => filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING) + ], + "description" => [ + "value" => filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING) + ], + "model" => [ + "type" => filter_input(INPUT_POST, "mtype", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "author", FILTER_SANITIZE_STRING), + "authorLink" => filter_input(INPUT_POST, "authorLink", FILTER_SANITIZE_STRING) + ], + "network" => [ + "value" => filter_input(INPUT_POST, "ntype", FILTER_SANITIZE_STRING) + ], + "language" => [ + "value" => filter_input(INPUT_POST, "language", FILTER_SANITIZE_STRING) + ], + "framework" => [ + "value" => filter_input(INPUT_POST, "framework", FILTER_SANITIZE_STRING) + ], + "toolkit" => [ + "value" => filter_input(INPUT_POST, "toolkit", FILTER_SANITIZE_STRING) + ], + "dataset" => [ + "name" => filter_input(INPUT_POST, "datasetUsed", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "datasetAuthor", FILTER_SANITIZE_STRING), + "url" => filter_input(INPUT_POST, "datasetLink", FILTER_SANITIZE_STRING), + "type" => filter_input(INPUT_POST, "datasetType", FILTER_SANITIZE_STRING), + "augmentation" => filter_input(INPUT_POST, "datasetAugmentation", FILTER_SANITIZE_NUMBER_INT) ? 1 : 0, + "positiveLabel" => filter_input(INPUT_POST, "datasetPosLabel", FILTER_SANITIZE_STRING), + "negativeLabel" => filter_input(INPUT_POST, "datasetNegLabel", FILTER_SANITIZE_STRING) + ], + "paper" => [ + "title" => filter_input(INPUT_POST, "relatedPaper", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "relatedPaperAuthor", FILTER_SANITIZE_STRING), + "doi" => filter_input(INPUT_POST, "relatedPaperDOI", FILTER_SANITIZE_STRING), + "link" => filter_input(INPUT_POST, "relatedPaperLink", FILTER_SANITIZE_STRING) + ], + "properties" => $properties, + "commands" => $commands, + "states" => $states, + "state" => $state, + "dateCreated" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + + $response = json_decode($this->contextBrokerRequest("POST", $this->cb["entities_url"] . "?type=Model", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + return [ + "Response"=> "OK", + "Message" => "Model Created!" + ]; + else: + return [ + "Response"=> "FAILED", + "Message" => "Model Created KO! " . $response["Description"] + ]; + endif; + } + + public function updateModel() + { + if(!filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Category is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Description is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ntype", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Network type is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ntype", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Network type is required" + ]; + endif; + + $properties=[]; + if(isSet($_POST["properties"])): + foreach($_POST["properties"] AS $key => $value): + $properties[$value] = ["value" => ""]; + endforeach; + endif; + + $commands=[]; + if(isSet($_POST["commands"])): + foreach($_POST["commands"] AS $key => $value): + $values = explode(",", $value); + $commands[$key] = $values; + endforeach; + endif; + + $states=[]; + $state=[]; + if(isSet($_POST["states"])): + $states = $_POST["states"]; + $state = [ + "value" => "", + "timestamp" => "" + ]; + endif; + + $model = $this->getModel(filter_input(INPUT_GET, 'model', FILTER_SANITIZE_NUMBER_INT)); + + $data = [ + "category" => [ + "value" => [filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)] + ], + "name" => [ + "value" => filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING) + ], + "description" => [ + "value" => filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING) + ], + "model" => [ + "type" => filter_input(INPUT_POST, "mtype", FILTER_SANITIZE_STRING), + "link" => filter_input(INPUT_POST, "link", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "author", FILTER_SANITIZE_STRING), + "authorLink" => filter_input(INPUT_POST, "authorLink", FILTER_SANITIZE_STRING) + ], + "network" => [ + "value" => filter_input(INPUT_POST, "ntype", FILTER_SANITIZE_STRING) + ], + "language" => [ + "value" => filter_input(INPUT_POST, "language", FILTER_SANITIZE_STRING) + ], + "framework" => [ + "value" => filter_input(INPUT_POST, "framework", FILTER_SANITIZE_STRING) + ], + "toolkit" => [ + "value" => filter_input(INPUT_POST, "toolkit", FILTER_SANITIZE_STRING) + ], + "dataset" => [ + "name" => filter_input(INPUT_POST, "datasetUsed", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "datasetAuthor", FILTER_SANITIZE_STRING), + "url" => filter_input(INPUT_POST, "datasetLink", FILTER_SANITIZE_STRING), + "type" => filter_input(INPUT_POST, "datasetType", FILTER_SANITIZE_STRING), + "augmentation" => filter_input(INPUT_POST, "datasetAugmentation", FILTER_SANITIZE_NUMBER_INT) ? 1 : 0, + "positiveLabel" => filter_input(INPUT_POST, "datasetPosLabel", FILTER_SANITIZE_STRING), + "negativeLabel" => filter_input(INPUT_POST, "datasetNegLabel", FILTER_SANITIZE_STRING) + ], + "paper" => [ + "title" => filter_input(INPUT_POST, "relatedPaper", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "relatedPaperAuthor", FILTER_SANITIZE_STRING), + "doi" => filter_input(INPUT_POST, "relatedPaperDOI", FILTER_SANITIZE_STRING), + "link" => filter_input(INPUT_POST, "relatedPaperLink", FILTER_SANITIZE_STRING) + ], + "properties" => $properties, + "commands" => $commands, + "states" => $states, + "state" => $state, + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $model["context"]["Data"]["id"] . "/attrs?type=Model", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + $model = $this->getModel(filter_input(INPUT_GET, 'model', FILTER_SANITIZE_NUMBER_INT)); + return [ + "Response"=> "OK", + "Message" => "Model Updated!" + ]; + else: + return [ + "Response"=> "FAILED", + "Message" => "Model Update KO! " . $response["Description"] + ]; + endif; + } + + } + + $AI = new AI($_GeniSys); + + if(filter_input(INPUT_POST, "create_ai_model", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($AI->createModel())); + endif; + + if(filter_input(INPUT_POST, "update_ai_model", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($AI->updateModel())); + endif; \ No newline at end of file diff --git a/Root/var/www/html/AI/Create.php b/Root/var/www/html/AI/Create.php new file mode 100644 index 0000000..d668834 --- /dev/null +++ b/Root/var/www/html/AI/Create.php @@ -0,0 +1,299 @@ + "AI", + "SubPageID" => "Models" +]; + +include dirname(__FILE__) . '/../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../AI/Classes/AI.php'; + +$_GeniSysAi->checkSession(); + +?> + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
Create AI Classifier Model
+
+
+
+
+
+
+
+
+
+
+
+ + + Name of Model +
+
+ + + Description of model +
+
+ + + Link to model +
+
+ + + Category of AI model +
+
+ + + Type of AI network +
+
+ + + Type of AI network +
+
+ + + Programming language used to develop the model +
+
+ + + Dataset used to train and test model +
+
+ + + Dataset used to train and test model +
+
+ + + Dataset used to train and test model +
+
+ + + Dataset author +
+
+ + + Dataset link +
+
+ + + Dataset Augmentation +
+
+ + + Dataset type +
+
+ + + To be used in the HIAS UI, test data is expected to include the label at the end of the file name. Specify the positive label here. +
+
+ + + To be used in the HIAS UI, test data is expected to include the label at the end of the file name. Specify the negative label here. +
+
+ + +
+
+
+
+ + + Author(s) +
+
+ + + Author Link +
+
+ + + Related research paper +
+
+ + + Related research paper author +
+
+ + + Related research paper DOI +
+
+ + + Related research paper link +
+
+ +
+
+ Model Properties +
+
+ +
+
+ Model Commands +
+
+ +
+
+ Model States +
+
+
+
+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + + + diff --git a/Root/var/www/html/GeniSysAI/Classes/NLU.js b/Root/var/www/html/AI/GeniSysAI/Classes/NLU.js similarity index 63% rename from Root/var/www/html/GeniSysAI/Classes/NLU.js rename to Root/var/www/html/AI/GeniSysAI/Classes/NLU.js index 5b51366..076ee08 100644 --- a/Root/var/www/html/GeniSysAI/Classes/NLU.js +++ b/Root/var/www/html/AI/GeniSysAI/Classes/NLU.js @@ -1,13 +1,12 @@ var NLU = { Create: function() { $.post(window.location.href, $("#genisysai_create").serialize(), function(resp) { - console.log(resp) var resp = jQuery.parseJSON(resp); switch (resp.Response) { case "OK": GeniSys.ResetForm("genisysai_create"); $('.modal-title').text('GeniSyAI Security Devices'); - $('.modal-body').html("HIAS GeniSyAI NLU Device ID #" + resp.GDID + " created! Please save the API keys safely. The device's credentials are provided below. The credentials can be reset in the GeniSyAI Security Devices area.

Device ID: " + resp.DID + "
MQTT User: " + resp.MU + "
MQTT Password: " + resp.MP + "

Blockchain User: " + resp.BU + "
Blockchain Pass: " + resp.BP + "

App ID: " + resp.AppID + "
App Key: " + resp.AppKey + "

" + resp.Message); + $('.modal-body').html("HIAS GeniSyAI NLU Device ID #" + resp.DID + " created! Please save the API keys safely. The device's credentials are provided below. The credentials can be reset in the GeniSyAI Security Devices area.

MQTT User: " + resp.MU + "
MQTT Password: " + resp.MP + "

Blockchain User: " + resp.BU + "
Blockchain Pass: " + resp.BP + "

App ID: " + resp.AppID + "
App Key: " + resp.AppKey + "

" + resp.Message); $('#responsive-modal').modal('show'); Logging.logMessage("Core", "Forms", "Device ID #" + resp.DID + " created!"); break; @@ -23,6 +22,8 @@ var NLU = { var resp = jQuery.parseJSON(resp); switch (resp.Response) { case "OK": + var fjson = JSON.stringify(resp.Schema, null, '\t'); + window.parent.$('#schema').html(fjson); $('.modal-title').text('NLU Update'); $('.modal-body').text("NLU Update OK"); $('#responsive-modal').modal('show'); @@ -36,15 +37,15 @@ var NLU = { }); }, ResetMqtt: function() { - $.post(window.location.href, { "reset_mqtt": 1, "id": $("#did").val(), "lid": $("#lid").val(), "zid": $("#zid").val() }, + $.post(window.location.href, { "reset_mqtt": 1 }, function(resp) { var resp = jQuery.parseJSON(resp); switch (resp.Response) { case "OK": Logging.logMessage("Core", "Forms", "Reset OK"); - GeniSysAI.mqttpa = resp.P; - GeniSysAI.mqttpae = resp.P.replace(/\S/gi, '*'); - $("#idmqttp").text(GeniSysAI.mqttpae); + NLU.mqttp3a = resp.P; + NLU.mqttp3ae = resp.P.replace(/\S/gi, '*'); + $("#mqttpt").text(NLU.mqttp3ae); $('.modal-title').text('New MQTT Password'); $('.modal-body').text("This device's new MQTT password is: " + resp.P); $('#responsive-modal').modal('show'); @@ -57,14 +58,14 @@ var NLU = { }); }, ResetDvcKey: function() { - $.post(window.location.href, { "reset_key": 1, "id": $("#did").val(), "lid": $("#lid").val(), "zid": $("#zid").val() }, + $.post(window.location.href, { "reset_key": 1 }, function(resp) { var resp = jQuery.parseJSON(resp); switch (resp.Response) { case "OK": Logging.logMessage("Core", "Forms", "Device Update OK"); $('.modal-title').text('Device Update'); - $('.modal-body').text(resp.Message); + $('.modal-body').text("This device's new key is: " + resp.P); $('#responsive-modal').modal('show'); break; default: @@ -74,6 +75,45 @@ var NLU = { } }); }, + ResetDvcAMQP: function() { + $.post(window.location.href, { "reset_dvc_amqp": 1 }, + function(resp) { + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + GeniSysAI.damqppa = resp.P; + GeniSysAI.damqppae = resp.P.replace(/\S/gi, '*'); + $("#damqpp").text(GeniSysAI.damqppae); + Logging.logMessage("Core", "Forms", resp.Message); + $('.modal-title').text('Reset Device AMQP Key'); + $('.modal-body').text("This device's new AMQP key is: " + resp.P); + $('#responsive-modal').modal('show'); + break; + default: + msg = "Reset failed: " + resp.Message + Logging.logMessage("Core", "Forms", msg); + $('.modal-title').text('Reset Device AMQP Key'); + $('.modal-body').text(msg); + $('#responsive-modal').modal('show'); + break; + } + }); + }, + ChatWithGeniSysAI: function() { + $("#chatWindow").prepend("
YOU: " + $("#GeniSysAiChat").val() + "
"); + $.post(window.location.href, $("#genisysai_chat").serialize(), + function(resp) { + console.log(resp) + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + $("#chatWindow").prepend("
GeniSysAI: " + resp.Message + "
"); + break; + default: + break; + } + }); + }, HideInputs: function() { $('#ip').attr('type', 'password'); $('#mac').attr('type', 'password'); @@ -81,6 +121,10 @@ var NLU = { $('#sportf').attr('type', 'password'); $('#sckport').attr('type', 'password'); + NLU.damqpua = $("#damqpu").text(); + NLU.damqpuae = $("#damqpu").text().replace(/\S/gi, '*'); + NLU.damqppa = $("#damqpp").text(); + NLU.damqppae = $("#damqpp").text().replace(/\S/gi, '*'); NLU.mqttu3a = $("#mqttut").text(); NLU.mqttu3ae = $("#mqttut").text().replace(/\S/gi, '*'); NLU.mqttp3a = $("#mqttpt").text(); @@ -90,13 +134,15 @@ var NLU = { NLU.bcida = $("#bcid").text(); NLU.bcidae = $("#bcid").text().replace(/\S/gi, '*'); + $("#damqpu").text(NLU.damqpuae); + $("#damqpp").text(NLU.damqppae); $("#mqttut").text(NLU.mqttu3ae); $("#mqttpt").text(NLU.mqttp3ae); $("#idappid").text(NLU.idappidae); $("#bcid").text(NLU.bcidae); }, GetLife: function() { - $.post(window.location.href, { "get_tlife": 1, "device": $("#did").val() }, function(resp) { + $.post(window.location.href, { "get_tlife": 1 }, function(resp) { var resp = jQuery.parseJSON(resp); switch (resp.Response) { case "OK": @@ -153,6 +199,16 @@ $(document).ready(function() { NLU.ResetDvcKey(); }); + $("#GeniSysAI").on("click", "#reset_dvc_amqp", function(e) { + e.preventDefault(); + NLU.ResetDvcAMQP(); + }); + + $("#GeniSysAI").on("click", "#send_chat", function(e) { + e.preventDefault(); + NLU.ChatWithGeniSysAI(); + }); + $('#idappid').hover(function() { $("#idappid").text(NLU.idappida); }, function() { @@ -179,6 +235,6 @@ $(document).ready(function() { setInterval(function() { NLU.GetLife(); - }, 5000); + }, 10000); }); \ No newline at end of file diff --git a/Root/var/www/html/AI/GeniSysAI/Classes/NLU.php b/Root/var/www/html/AI/GeniSysAI/Classes/NLU.php new file mode 100644 index 0000000..43cc2b4 --- /dev/null +++ b/Root/var/www/html/AI/GeniSysAI/Classes/NLU.php @@ -0,0 +1,1673 @@ +_GeniSys = $_GeniSys; + $this->bcc = $this->getBlockchainConf(); + $this->web3 = $this->blockchainConnection(); + $this->contract = new Contract($this->web3->provider, $this->bcc["abi"]); + $this->icontract = new Contract($this->web3->provider, $this->bcc["iabi"]); + $this->checkBlockchainPermissions(); + $this->cb = $this->getContextBrokerConf(); + } + + public function getContextBrokerConf() + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM contextbroker + "); + $pdoQuery->execute(); + $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + return $response; + } + + private function createContextHeaders() + { + $basicAuth = $_SESSION["GeniSysAI"]["User"] . ":" . $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"]); + $basicAuth = base64_encode($basicAuth); + + return [ + "Content-Type: application/json", + 'Authorization: Basic '. $basicAuth + ]; + } + + private function contextBrokerRequest($method, $endpoint, $headers, $json) + { + $path = $this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/" . $this->cb["url"] . "/" . $endpoint; + + if($method == "GET"): + $ch = curl_init(); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_URL, $path); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "POST"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "PATCH"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + endif; + + return $body; + } + + public function getBlockchainConf() + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT blockchain.*, + contracts.contract, + contracts.abi, + icontracts.contract as icontract, + icontracts.abi as iabi + FROM blockchain blockchain + INNER JOIN contracts contracts + ON contracts.id = blockchain.dc + INNER JOIN contracts icontracts + ON icontracts.id = blockchain.ic + "); + $pdoQuery->execute(); + $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + return $response; + } + + private function blockchainConnection() + { + $web3 = new Web3($this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/Blockchain/API/", 30, $_SESSION["GeniSysAI"]["User"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"])); + + return $web3; + } + + private function checkBlockchainPermissions() + { + $allowed = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->call("identifierAllowed", "User", $_SESSION["GeniSysAI"]["Identifier"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$allowed) { + if ($err !== null) { + $allowed = "FAILED"; + return; + } + $allowed = $resp[0]; + }); + + if($allowed != "true"): + header('Location: /Logout'); + endif; + } + + private function unlockBlockchainAccount() + { + $response = ""; + $personal = $this->web3->personal; + $personal->unlockAccount($_SESSION["GeniSysAI"]["BC"]["BCUser"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["BC"]["BCPass"]), function ($err, $unlocked) use (&$response) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + if ($unlocked) { + $response = "OK"; + } else { + $response = "FAILED"; + } + }); + + return $response; + } + + private function createBlockchainUser($pass) + { + $newAccount = ""; + $personal = $this->web3->personal; + $personal->newAccount($pass, function ($err, $account) use (&$newAccount) { + if ($err !== null) { + $newAccount = "FAILED!"; + return; + } + $newAccount = $account; + }); + + return $newAccount; + } + + private function getBlockchainBalance() + { + $nbalance = ""; + $this->web3->eth->getBalance($_SESSION["GeniSysAI"]["BC"]["BCUser"], function ($err, $balance) use (&$nbalance) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + $nbalance = $balance->toString(); + }); + + return Utils::fromWei($nbalance, 'ether')[0]; + } + + private function storeBlockchainTransaction($action, $hash, $device = 0, $application = 0) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + INSERT INTO transactions ( + `uid`, + `did`, + `aid`, + `action`, + `hash`, + `time` + ) VALUES ( + :uid, + :did, + :aid, + :action, + :hash, + :time + ) + "); + $pdoQuery->execute([ + ":uid" => $_SESSION["GeniSysAI"]["Uid"], + ":did" => $device, + ":aid" => $application, + ":action" => $action, + ':hash' => $this->_GeniSys->_helpers->oEncrypt($hash), + ":time" => time() + ]); + $txid = $this->_GeniSys->_secCon->lastInsertId(); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + return $txid; + } + + private function storeUserHistory($action, $hash, $location = 0, $zone = 0, $device = 0, $sensor = 0, $application = 0) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + INSERT INTO history ( + `uid`, + `tlid`, + `tzid`, + `tdid`, + `tsid`, + `taid`, + `action`, + `hash`, + `time` + ) VALUES ( + :uid, + :tlid, + :tzid, + :tdid, + :tsid, + :taid, + :action, + :hash, + :time + ) + "); + $pdoQuery->execute([ + ":uid" => $_SESSION["GeniSysAI"]["Uid"], + ":tlid" => $location, + ":tzid" => $zone, + ":tdid" => $device, + ":tsid" => $sensor, + ":taid" => $application, + ":action" => $action, + ":hash" => $hash, + ":time" => time() + ]); + $txid = $this->_GeniSys->_secCon->lastInsertId(); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + return $txid; + } + + private function addAmqpUser($username, $key) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpu ( + `username`, + `pw` + ) VALUES ( + :username, + :pw + ) + "); + $query->execute([ + ':username' => $username, + ':pw' => $this->_GeniSys->_helpers->oEncrypt($key) + ]); + $amid = $this->_GeniSys->_secCon->lastInsertId(); + return $amid; + } + + private function addAmqpUserVh($uid, $vhost) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvh ( + `uid`, + `vhost` + ) VALUES ( + :uid, + :vhost + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost + ]); + } + + private function addAmqpVhPerm($uid, $vhost, $rtype, $rname, $permission) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvhr ( + `uid`, + `vhost`, + `rtype`, + `rname`, + `permission` + ) VALUES ( + :uid, + :vhost, + :rtype, + :rname, + :permission + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost, + ':rtype' => $rtype, + ':rname' => $rname, + ':permission' => $permission + ]); + } + + private function addAmqpVhTopic($uid, $vhost, $rtype, $rname, $permission, $rkey) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvhrt ( + `uid`, + `vhost`, + `rtype`, + `rname`, + `permission`, + `rkey` + ) VALUES ( + :uid, + :vhost, + :rtype, + :rname, + :permission, + :rkey + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost, + ':rtype' => $rtype, + ':rname' => $rname, + ':permission' => $permission, + ':rkey' => $rkey + ]); + } + + public function checkLocation($lid) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT id + FROM mqttl + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $lid + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($location["id"]): + return True; + else: + return False; + endif; + } + + public function getLocation($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttl + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $location["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $location["pub"] . "?type=Location" . $attrs, $this->createContextHeaders(), []), true); + return $location; + } + + public function checkZone($zid) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT id + FROM mqttlz + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $zid + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($location["id"]): + return True; + else: + return False; + endif; + } + + public function getZone($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttlz + WHERE id = :id + ORDER BY id DESC + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $zone=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $zone["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $zone["pub"] . "?type=Zone" . $attrs, $this->createContextHeaders(), []), true); + return $zone; + } + + public function getDevices($limit = 0) + { + $limiter = ""; + if($limit != 0): + $limiter = "&limit=" . $limit; + endif; + + $devices = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "?type=Device&category=GeniSysAI".$limiter, $this->createContextHeaders(), []), true); + return $devices; + } + + public function getDevice($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttld + WHERE id = :id + ORDER BY id DESC + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $device=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $device["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $device["apub"] . "?type=Device" . $attrs, $this->createContextHeaders(), []), true); + return $device; + } + + public function getThing($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM things + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $thing=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $thing["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $thing["pub"] . "?type=Thing" . $attrs, $this->createContextHeaders(), []), true); + return $thing; + } + + public function getModel($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM models + WHERE id = :id + ORDER BY id DESC + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $model=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $device["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $model["pub"] . "?type=Model" . $attrs, $this->createContextHeaders(), []), true); + return $device; + } + + public function createDevice() + { + if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Location ID is required" + ]; + endif; + + if(!$this->checkLocation(filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Zone ID is required" + ]; + endif; + + if(!$this->checkZone(filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay zone does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Category is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Coordinates entity is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device model is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system version is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IP is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "MAC is required" + ]; + endif; + + if(!count($_POST["protocols"])): + return [ + "Response"=> "Failed", + "Message" => "At least one M2M protocol is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "apidir", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Nginx server proxy path" + ]; + endif; + + $mqttUser = $this->_GeniSys->_helpers->generate_uuid(); + $mqttPass = $this->_GeniSys->_helpers->password(); + $mqttHash = create_hash($mqttPass); + + $pubKey = $this->_GeniSys->_helpers->generate_uuid(); + $privKey = $this->_GeniSys->_helpers->generateKey(32); + $privKeyHash = $this->_GeniSys->_helpers->createPasswordHash($privKey); + + $amqppubKey = $this->_GeniSys->_helpers->generate_uuid(); + $amqpprvKey = $this->_GeniSys->_helpers->generateKey(32); + $amqpKeyHash = $this->_GeniSys->_helpers->createPasswordHash($amqpprvKey); + + $bcPass = $this->_GeniSys->_helpers->password(); + + $lid = filter_input(INPUT_POST, 'lid', FILTER_SANITIZE_NUMBER_INT); + $location = $this->getLocation($lid); + + $zid = filter_input(INPUT_POST, 'zid', FILTER_SANITIZE_NUMBER_INT); + $zone = $this->getZone($zid); + + $ip = filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING); + $mac = filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING); + + $ip = filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING); + $mac = filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING); + $bluetooth = filter_input(INPUT_POST, "bluetooth", FILTER_SANITIZE_STRING); + $name = filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING); + $coords = explode(",", filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)); + + $protocols = []; + foreach($_POST["protocols"] AS $key => $value): + $protocols[] = $value; + endforeach; + + $models = []; + if(isSet($_POST["ai"])): + foreach($_POST["ai"] AS $key => $value): + $model = $this->getModel($value)["context"]["Data"]; + $mname = $model["name"]["value"]; + unset($model["id"]); + unset($model["type"]); + unset($model["mid"]); + unset($model["name"]); + unset($model["description"]); + unset($model["network"]); + unset($model["language"]); + unset($model["framework"]); + unset($model["toolkit"]); + unset($model["dateCreated"]); + unset($model["dateModified"]); + $models[$mname] = $model; + endforeach; + endif; + + $sensors = []; + if(isSet($_POST["sensors"])): + foreach($_POST["sensors"] AS $key => $value): + $sensor = $this->getThing($value)["context"]["Data"]; + unset($sensor["id"]); + unset($sensor["type"]); + unset($sensor["category"]); + unset($sensor["description"]); + unset($sensor["thing"]); + unset($sensor["properties"]["image"]); + unset($sensor["dateCreated"]); + unset($sensor["dateModified"]); + $sensors[] = $sensor; + endforeach; + endif; + + $actuators = []; + if(isSet($_POST["actuators"])): + foreach($_POST["actuators"] AS $key => $value): + $actuator = $this->getThing($value)["context"]["Data"]; + unset($actuator["id"]); + unset($actuator["type"]); + unset($actuator["category"]); + unset($actuator["description"]); + unset($actuator["thing"]); + unset($actuator["properties"]["image"]); + unset($actuator["dateCreated"]); + unset($actuator["dateModeified"]); + $actuators[] = $actuator; + endforeach; + endif; + + $newBcUser = $this->createBlockchainUser($bcPass); + + if($newBcUser == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Creating New HIAS Blockhain Account Failed!" + ]; + endif; + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttld ( + `apub` + ) VALUES ( + :apub + ) + "); + $query->execute([ + ':apub' => $pubKey + ]); + $did = $this->_GeniSys->_secCon->lastInsertId(); + + $data = [ + "id" => $pubKey, + "type" => "Device", + "category" => [ + "value" => [filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)] + ], + "name" => [ + "value" => $name + ], + "description" => [ + "value" => filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING) + ], + "lid" => [ + "value" => $lid, + "entity" => $location["context"]["Data"]["id"] + ], + "zid" => [ + "value" => $zid, + "entity" => $zone["context"]["Data"]["id"] + ], + "did" => [ + "value" => $did + ], + "location" => [ + "type" => "geo:json", + "value" => [ + "type" => "Point", + "coordinates" => [floatval($coords[0]), floatval($coords[1])] + ] + ], + "agent" => [ + "url" => filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING) + ], + "device" => [ + "type" => filter_input(INPUT_POST, "type", FILTER_SANITIZE_STRING), + "name" => filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING), + "model" => filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING) + ], + "proxy" => [ + "endpoint" => filter_input(INPUT_POST, "apidir", FILTER_SANITIZE_STRING) + ], + "stream" => [ + "port" => "", + "file" => "" + ], + "socket" => [ + "port" => "" + ], + "os" => [ + "name" => filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING), + "version" => filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING) + ], + "protocols" => $protocols, + "status" => [ + "value" => "OFFLINE", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "keys" => [ + "public" => $pubKey, + "private" => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "blockchain" => [ + "address" => $newBcUser, + "password" => $this->_GeniSys->_helpers->oEncrypt($bcPass) + ], + "mqtt" => [ + "username" => $this->_GeniSys->_helpers->oEncrypt($mqttUser), + "password" => $this->_GeniSys->_helpers->oEncrypt($mqttPass), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "coap" => [ + "username" => "", + "password" => "" + ], + "amqp" => [ + "username" => $this->_GeniSys->_helpers->oEncrypt($amqppubKey), + "password" => $this->_GeniSys->_helpers->oEncrypt($amqpprvKey), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "batteryLevel" => [ + "value" => 0.00 + ], + "cpuUsage" => [ + "value" => 0.00 + ], + "memoryUsage" => [ + "value" => 0.00 + ], + "hddUsage" => [ + "value" => 0.00 + ], + "temperature" => [ + "value" => 0.00 + ], + "ip" => [ + "value" => $ip, + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "mac" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($mac), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "bluetooth" => [ + "address" => $bluetooth ? $this->_GeniSys->_helpers->oEncrypt($bluetooth) : "", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "ai" => $models, + "sensors" => $sensors, + "actuators" => $actuators, + "dateCreated" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateFirstUsed" => [ + "type" => "DateTime", + "value" => "" + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + + $response = json_decode($this->contextBrokerRequest("POST", $this->cb["entities_url"] . "?type=Device", $this->createContextHeaders(), json_encode($data)), true); + if($response["Response"]=="OK"): + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttu ( + `lid`, + `zid`, + `did`, + `uname`, + `pw` + ) VALUES ( + :lid, + :zid, + :did, + :uname, + :pw + ) + "); + $query->execute([ + ':lid' => $lid, + ':zid' => $zid, + ':did' => $did, + ':uname' => $mqttUser, + ':pw' => $mqttHash + ]); + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttua ( + `lid`, + `zid`, + `did`, + `username`, + `topic`, + `rw` + ) VALUES ( + :lid, + :zid, + :did, + :username, + :topic, + :rw + ) + "); + $query->execute(array( + ':lid' => $lid, + ':zid' => $zid, + ':did' => $did, + ':username' => $mqttUser, + ':topic' => $location["context"]["Data"]["id"] . "/Devices/" . $zone["context"]["Data"]["id"] . "/" . $pubKey . "/#", + ':rw' => 4 + )); + + $amid = $this->addAmqpUser($amqppubKey, $amqpKeyHash); + $this->addAmqpUserVh($amid, "iotJumpWay"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "exchange", "Core", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "exchange", "Core", "write"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Life", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Life", "write"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Statuses", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Statuses", "write"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "read", "Life"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "write", "Life"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "read", "Statuses"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "write", "Statuses"); + + $unlocked = $this->unlockBlockchainAccount(); + + if($unlocked == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Unlocking HIAS Blockhain Account Failed!" + ]; + endif; + + $hash = ""; + $msg = ""; + $actionMsg = ""; + $balanceMessage = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("registerDevice", $pubKey, $newBcUser, $lid, $zid, $did, $name, $_SESSION["GeniSysAI"]["Uid"], time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg = " HIAS Blockchain registerDevice failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("Register Device", $hash, $did); + $this->storeUserHistory("Register Device", $txid, $lid, $zid, $did); + $balance = $this->getBlockchainBalance(); + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!\n"; + endif; + + $this->icontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["icontract"]))->send("registerAuthorized", $newBcUser, ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg .= " HIAS Blockchain registerAuthorized failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("iotJumpWay Register Authorized", $hash, $did); + $this->storeUserHistory("Register Authorized", $txid, $lid, $zid, $did); + $balance = $this->getBlockchainBalance(); + if($balanceMessage == ""): + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!\n"; + endif; + endif; + + return [ + "Response"=> "OK", + "Message" => "Device created!" . $actionMsg . $balanceMessage, + "LID" => $lid, + "ZID" => $zid, + "DID" => $did, + "MU" => $mqttUser, + "MP" => $mqttPass, + "BU" => $newBcUser, + "BP" => $bcPass, + "AppID" => $pubKey, + "AppKey" => $privKey + ]; + else: + return [ + "Response"=> "FAILED", + "Message" => "Device creation failed" + ]; + endif; + } + + public function updateDevice() + { + + if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Location ID is required" + ]; + endif; + + if(!$this->checkLocation(filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Zone ID is required" + ]; + endif; + + if(!$this->checkZone(filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Category is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Description is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Coordinates entity is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device model is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system version is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system version is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IP is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "MAC is required" + ]; + endif; + + if(!count($_POST["protocols"])): + return [ + "Response"=> "Failed", + "Message" => "At least one M2M protocol is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "apidir", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Device proxy url is required" + ]; + endif; + + $unlocked = $this->unlockBlockchainAccount(); + + if($unlocked == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Unlocking HIAS Blockhain Account Failed!" + ]; + endif; + + $ip = filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING); + $mac = filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING); + $bluetooth = filter_input(INPUT_POST, "bluetooth", FILTER_SANITIZE_STRING); + $name = filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING); + $status = filter_input(INPUT_POST, "status", FILTER_SANITIZE_STRING); + $coords = explode(",", filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)); + + $did = filter_input(INPUT_GET, "device", FILTER_SANITIZE_NUMBER_INT); + $device = $this->getDevice($did); + + $lid = filter_input(INPUT_POST, 'lid', FILTER_SANITIZE_NUMBER_INT); + $location = $this->getLocation($lid); + + $zid = filter_input(INPUT_POST, 'zid', FILTER_SANITIZE_NUMBER_INT); + $zone = $this->getZone($zid); + + $protocols = []; + foreach($_POST["protocols"] AS $key => $value): + $protocols[] = $value; + endforeach; + + $models = []; + if(isSet($_POST["ai"])): + foreach($_POST["ai"] AS $key => $value): + $model = $this->getModel($value)["context"]["Data"]; + $mname = $model["name"]["value"]; + unset($model["id"]); + unset($model["type"]); + unset($model["mid"]); + unset($model["name"]); + unset($model["description"]); + unset($model["network"]); + unset($model["language"]); + unset($model["framework"]); + unset($model["toolkit"]); + unset($model["dateCreated"]); + unset($model["dateModified"]); + $models[$mname] = $model; + endforeach; + endif; + + $sensors = []; + if(isSet($_POST["sensors"])): + foreach($_POST["sensors"] AS $key => $value): + $sensor = $this->getThing($value)["context"]["Data"]; + unset($sensor["id"]); + unset($sensor["type"]); + unset($sensor["category"]); + unset($sensor["description"]); + unset($sensor["thing"]); + unset($sensor["properties"]["image"]); + unset($sensor["dateCreated"]); + unset($sensor["dateModified"]); + $sensors[] = $sensor; + endforeach; + endif; + + $actuators = []; + if(isSet($_POST["actuators"])): + foreach($_POST["actuators"] AS $key => $value): + $actuator = $this->getThing($value)["context"]["Data"]; + unset($actuator["id"]); + unset($actuator["type"]); + unset($actuator["category"]); + unset($actuator["description"]); + unset($actuator["thing"]); + unset($actuator["properties"]["image"]); + unset($actuator["dateCreated"]); + unset($actuator["dateModeified"]); + $actuators[] = $actuator; + endforeach; + endif; + + if($device["context"]["Data"]["lid"]["value"] != $lid): + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttu + SET lid = :lid + WHERE did = :did + "); + $query->execute([ + ':lid' => $lid, + ':did' => $did + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttua + SET lid = :lid + WHERE did = :did + "); + $query->execute([ + ':lid' => $lid, + ':did' => $did + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttua + SET topic = :topicN + WHERE did = :did + & topic = :topic + "); + $query->execute([ + ':topicN' => $device["context"]["Data"]["lid"]["entity"] . "/ " . $device["context"]["Data"]["zid"]["entity"] . "/Devices/" . $device["context"]["Data"]["id"] . "/#", + ':did' => $did, + ':topic' => $location["context"]["Data"]["id"] . "/Devices/" . $zone["context"]["Data"]["id"] . "/Devices/" . $device["context"]["Data"]["id"] . "/#" + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + endif; + + $data = [ + "category" => [ + "value" => [filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)] + ], + "type" => "Device", + "name" => [ + "value" => $name + ], + "description" => [ + "value" => filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING) + ], + "lid" => [ + "value" => $lid, + "entity" => $location["context"]["Data"]["id"] + ], + "zid" => [ + "value" => $zid, + "entity" => $zone["context"]["Data"]["id"] + ], + "did" => [ + "value" => $did + ], + "location" => [ + "type" => "geo:json", + "value" => [ + "type" => "Point", + "coordinates" => [floatval($coords[0]), floatval($coords[1])] + ] + ], + "agent" => [ + "url" => filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING) + ], + "device" => [ + "type" => "NLU", + "name" => filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING), + "model" => filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING) + ], + "proxy" => [ + "endpoint" => filter_input(INPUT_POST, "apidir", FILTER_SANITIZE_STRING) + ], + "os" => [ + "name" => filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING), + "version" => filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING) + ], + "protocols" => $protocols, + "status" => [ + "value" => $device["context"]["Data"]["status"]["value"], + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "ip" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($ip), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "mac" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($mac), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "bluetooth" => [ + "address" => $bluetooth ? $this->_GeniSys->_helpers->oEncrypt($bluetooth) : "", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "ai" => $models, + "sensors" => $sensors, + "actuators" => $actuators, + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $device["context"]["Data"]["id"] . "/attrs?type=Device", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + + $hash = ""; + $msg = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("updateDevice", $device["context"]["Data"]["id"], "Device", $lid, $zid, $did, $name, $device["context"]["Data"]["status"]["value"], time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + $balance = ""; + $balanceMessage = ""; + $actionMsg = ""; + if($hash == "FAILED"): + $actionMsg = " HIAS Blockchain updateDevice failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("Update Device", $hash, $did); + $this->storeUserHistory("Updated Device", $txid, $lid, $zid, $did); + $balance = $this->getBlockchainBalance(); + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!\n"; + endif; + + $device = $this->getDevice($did); + + return [ + "Response"=> "OK", + "Message" => "Device updated!" . $actionMsg . $balanceMessage, + "Schema" => $device["context"]["Data"] + ]; + else: + return [ + "Response"=> "Failed", + "Message" => "There was a problem updating this device context data!" + ]; + endif; + } + + public function resetDvcMqtt() + { + $id = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_NUMBER_INT); + $Device = $this->getDevice($id); + + $mqttPass = $this->_GeniSys->_helpers->password(); + $mqttHash = create_hash($mqttPass); + + $data = [ + "mqtt" => [ + "username" => $Device["context"]["Data"]["mqtt"]["username"], + "password" => $this->_GeniSys->_helpers->oEncrypt($mqttPass), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $Device["context"]["Data"]["id"] . "/attrs?type=Device", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttu + SET pw = :pw + WHERE did = :did + "); + $query->execute(array( + ':pw' => $mqttHash, + ':did' => $id + )); + + $this->storeUserHistory("Reset Device MQTT Password", 0, $Device["context"]["Data"]["lid"]["value"], $Device["context"]["Data"]["zid"]["value"], $id); + + return [ + "Response"=> "OK", + "Message" => "MQTT password reset!", + "P" => $mqttPass + ]; + else: + return [ + "Response"=> "FAILED", + "Message" => "MQTT password reset failed!" + ]; + endif; + } + + public function resetDvcKey() + { + $id = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_NUMBER_INT); + $Device = $this->getDevice($id); + + $privKey = $this->_GeniSys->_helpers->generateKey(32); + $privKeyHash = $this->_GeniSys->_helpers->createPasswordHash($privKey); + + $data = [ + "keys" => [ + "public" => $Device["context"]["Data"]["keys"]["public"], + "private" => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $Device["context"]["Data"]["id"] . "/attrs?type=Device", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + $this->storeUserHistory("Reset Device Key", 0, $Device["context"]["Data"]["lid"]["value"], $Device["context"]["Data"]["zid"]["value"], $id); + return [ + "Response"=> "OK", + "Message" => "Device key reset!", + "P" => $privKey + ]; + else: + return [ + "Response"=> "FAILED", + "Message" => "Device key reset failed!" + ]; + endif; + + } + + public function resetDvcAmqpKey() + { + $id = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_NUMBER_INT); + $Device = $this->getDevice($id); + + $amqpPass = $this->_GeniSys->_helpers->password(); + $amqpHash = $this->_GeniSys->_helpers->createPasswordHash($amqpPass); + + $data = [ + "amqp" => [ + "username" => $Device["context"]["Data"]["amqp"]["username"], + "password" => $this->_GeniSys->_helpers->oEncrypt($amqpPass), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $Device["context"]["Data"]["id"] . "/attrs?type=Device", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE amqpu + SET pw = :pw + WHERE username = :username + "); + $query->execute(array( + ':pw' => $this->_GeniSys->_helpers->oEncrypt($amqpHash), + ':username' => $this->_GeniSys->_helpers->oDecrypt($Device["context"]["Data"]["amqp"]["username"]) + )); + + $this->storeUserHistory("Reset Device AMQP Key", 0, $Device["context"]["Data"]["lid"]["value"], $Device["context"]["Data"]["zid"]["value"], $id); + + return [ + "Response"=> "OK", + "Message" => "AMQP password reset!", + "P" => $amqpPass + ]; + else: + return [ + "Response"=> "FAILED", + "Message" => "AMQP password reset failed!" + ]; + endif; + } + + public function getLife() + { + $Device = $this->getDevice(filter_input(INPUT_GET, 'device', FILTER_SANITIZE_NUMBER_INT), "batteryLevel,cpuUsage,memoryUsage,hddUsage,temperature,status"); + + if($Device["context"]["Response"]=="OK"): + $response = [ + "battery" => $Device["context"]["Data"]["batteryLevel"]["value"], + "cpu" => $Device["context"]["Data"]["cpuUsage"]["value"], + "mem" => $Device["context"]["Data"]["memoryUsage"]["value"], + "hdd" => $Device["context"]["Data"]["hddUsage"]["value"], + "tempr" => $Device["context"]["Data"]["temperature"]["value"], + "status" => $Device["context"]["Data"]["status"]["value"] + ]; + return [ + 'Response' => 'OK', + 'ResponseData' => $response + ]; + else: + return [ + 'Response'=>'FAILED' + ]; + endif; + } + + + public function chatWithGeniSysAI() + { + if(!filter_input(INPUT_POST, "GeniSysAiChat", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Please enter some text" + ]; + endif; + + $did = filter_input(INPUT_GET, "device", FILTER_SANITIZE_NUMBER_INT); + $device = $this->getDevice($did); + + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + INSERT INTO genisysai ( + `uid`, + `device`, + `chat`, + `timestamp` + ) VALUES ( + :uid, + :device, + :chat, + :timestamp + ) + "); + $pdoQuery->execute([ + ":uid" => $_SESSION["GeniSysAI"]["Uid"], + ":device" => $device["context"]["Data"]["id"], + ":chat" => filter_input(INPUT_POST, 'GeniSysAiChat', FILTER_SANITIZE_STRING), + ":timestamp" => time() + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + $path = $this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/GeniSysAI/NLU/API/Api"; + + $json = json_encode(["query" => filter_input(INPUT_POST, 'GeniSysAiChat', FILTER_SANITIZE_STRING)]); + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $this->createContextHeaders()); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + $response = json_decode($body, True); + + if($response["Response"]=="OK"): + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + INSERT INTO genisysai ( + `isGeniSys`, + `device`, + `chat`, + `timestamp` + ) VALUES ( + :isGeniSys, + :device, + :chat, + :timestamp + ) + "); + $pdoQuery->execute([ + ":isGeniSys" => 1, + ":device" => $device["context"]["Data"]["id"], + ":chat" => $response["ResponseData"][0]["Response"], + ":timestamp" => time() + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + return [ + "Response"=> "OK", + "Message" => $response["ResponseData"][0]["Response"] + ]; + else: + return [ + "Response"=> "Failed", + "Message" => "There was a problem communicating with GeniSysAI" + ]; + endif; + } + + } + + $NLU = new NLU($_GeniSys); + + if(filter_input(INPUT_POST, "update_genisysai", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($NLU->updateDevice())); + endif; + if(filter_input(INPUT_POST, "create_genisysai", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($NLU->createDevice())); + endif; + if(filter_input(INPUT_POST, "reset_mqtt", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($NLU->resetDvcMqtt())); + endif; + if(filter_input(INPUT_POST, "reset_key", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($NLU->resetDvcKey())); + endif; + if(filter_input(INPUT_POST, "reset_key", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($NLU->resetDvcKey())); + endif; + if(filter_input(INPUT_POST, "reset_dvc_amqp", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($NLU->resetDvcAmqpKey())); + endif; + if(filter_input(INPUT_POST, "get_tlife", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($NLU->getLife())); + endif; + if(filter_input(INPUT_POST, "chatToGeniSys", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($NLU->chatWithGeniSysAI())); + endif; \ No newline at end of file diff --git a/Root/var/www/html/AI/GeniSysAI/Create.php b/Root/var/www/html/AI/GeniSysAI/Create.php new file mode 100644 index 0000000..aec0241 --- /dev/null +++ b/Root/var/www/html/AI/GeniSysAI/Create.php @@ -0,0 +1,385 @@ + "AI", + "SubPageID" => "GeniSysAI" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/GeniSysAI/Classes/NLU.php'; + +$_GeniSysAi->checkSession(); + +$Zones = $iotJumpWay->getZones(); +$Devices = $iotJumpWay->getDevices(); + +?> + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
Create GeniSysAI NLU Device
+
+
+
+
+
+
+
+
+
+
+
+ + + Name of device +
+
+ + + Description of device +
+
+ + + Device category +
+
+ + + Device IoT Agent +
+
+ + + Name of hardware device +
+
+ + + Name of hardware manufacturer +
+
+ + + Hardware model +
+
+ + + Operating system name +
+
+ + + Operating system manufacturer +
+
+ + + Operating system version +
+
+ + + Supported Communication Protocols +
+
+ +
+
+ $value): + ?> + +
+
+ + " required> +
+
+ +
+
+ + +
+ Device Sensors + +
+
+ +
+
+ $value): + ?> + +
+
+ + " required> +
+
+ +
+
+ + +
+ Device Actuators + +
+
+ + +
+
+
+
+ + + Location of device +
+
+ + + Zone of device +
+
+ + + iotJumpWay Device coordinates +
+
+ + + IP of device +
+
+ + + MAC of device +
+
+ + + Bluetooth address of device +
+
+ + + Nginx server proxy path +
+
+ +
+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + + + diff --git a/Root/var/www/html/AI/GeniSysAI/Device.php b/Root/var/www/html/AI/GeniSysAI/Device.php new file mode 100644 index 0000000..7d76838 --- /dev/null +++ b/Root/var/www/html/AI/GeniSysAI/Device.php @@ -0,0 +1,913 @@ + "AI", + "SubPageID" => "GeniSysAI" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/GeniSysAI/Classes/NLU.php'; + +$_GeniSysAi->checkSession(); + +$Locations = $iotJumpWay->getLocations(); +$Zones = $iotJumpWay->getZones(); + +$TId = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_NUMBER_INT); +$Device = $iotJumpWay->getDevice($TId); + +list($dev1On, $dev1Off) = $iotJumpWay->getStatusShow($Device["context"]["Data"]["status"]["value"]); + +?> + + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
GeniSysAI Security NLU Device #
+
+
+
+
+
+
+
+
+
+
+
+ + "> + Name of device +
+
+ + "> + Description of device +
+
+ + + Device category +
+
+ + + Device IoT Agent +
+
+ + "> + Name of hardware device +
+
+ + "> + Name of hardware manufacturer +
+
+ + "> + Hardware model +
+
+ + "> + Operating system name +
+
+ + "> + Operating system manufacturer +
+
+ + "> + Operating system version +
+
+ + + Supported Communication Protocols +
+
+ +
+
+ $value): + ?> + +
+
+ + " required> +
+
+ +
+
+ + +
+ Device Sensors + +
+
+ +
+
+ $value): + ?> + +
+
+ + " required> +
+
+ +
+
+ + +
+ Device Actuators + +
+
+ +

+
+
+ +

+
+
+ +

+
+
+ + +
+
+
+
+ + + Location of device +
+
+ + + Zone of device +
+
+ + , "> + iotJumpWay Device coordinates +
+
+ + _helpers->oDecrypt($Device["context"]["Data"]["ip"]["value"]) : ""; ?>"> + IP of device +
+
+ + _helpers->oDecrypt($Device["context"]["Data"]["mac"]["value"]) : ""; ?>"> + MAC of device +
+
+ + _helpers->oDecrypt($Device["context"]["Data"]["bluetooth"]["address"]) : ""; ?>"> + Bluetooth address of device +
+
+ + "> + Nginx server proxy path +
+
+
+
+
+
+
+

+
+
+
+
Device Schema
+
+
+
+
+
+
+
+ "; ?> "; ?> +
+
+
+

+
+ +
+
+
+
+ + + + + + + + + + + + retrieveDeviceHistory($Device["context"]["Data"]["did"]["value"], 5); + if(count($history)): + foreach($history as $key => $value): + if($value["uid"]): + $user = $_GeniSysAi->getUser($value["uid"]); + $userDetails = "User ID #" . $value["uid"] . " (" . $user["name"] . ") "; + endif; + ?> + + + + + + + + + + + +
IDActionReceiptTime
# + + + /Zones//Devices//Transaction/"># + + NA + + + + +
+
+
+
+
+

+
+ +
+
+
+
+ + + + + + + + + + + + retrieveDeviceTransactions($Device["context"]["Data"]["did"]["value"], 5); + if(count($transactions)): + foreach($transactions as $key => $value): + if($value["uid"]): + $user = $_GeniSysAi->getUser($value["uid"]); + $userDetails = "User ID #" . $value["uid"] . " (" . $user["name"] . ") "; + endif; + ?> + + + + + + + + + + + +
IDActionReceiptTime
#/Zones//Devices//Transaction/">#
+
+
+
+
+

+
+
+
+
Device iotJumpWay Statuses
+
+ +
+
+
+
+
+
+ + + + + + + + + + + retrieveDeviceStatuses($Device["context"]["Data"]["id"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + ?> + + + + + + + + + + +
IDStatusTime
#_id;?>Status;?>Time;?>
+
+
+
+
+

+
+
+
+
Device iotJumpWay Life
+
+ +
+
+
+
+
+
+ + + + + + + + + + + retrieveDeviceLife($Device["context"]["Data"]["id"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + ?> + + + + + + + + + + +
IDDetailsTime
#_id;?> + CPU: Data->CPU;?>%
+ Memory: Data->Memory;?>%
+ Diskspace: Data->Diskspace;?>%
+ Temperature: Data->Temperature;?>°C
+ Latitude: Data->Latitude;?>
+ Longitude: Data->Longitude;?>
+
Time;?>
+
+
+
+
+

+
+
+
+
Device iotJumpWay Sensors
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + retrieveDeviceSensors($Device["context"]["Data"]["id"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + $location = $iotJumpWay->getLocation($value->Location); + ?> + + + + + + + + + + + + +
IDTypeSensorValueMessageTime
#_id;?>Type;?>Sensor;?> + Sensor == "Facial API" || $value->Sensor == "Foscam Camera" || $value->Sensor == "USB Camera") && is_array($value->Value)): + foreach($value->Value AS $key => $val): + echo $val[0] == 0 ? "Identification: Intruder
" :"Identification: User #" . $val[0] . "
"; + echo "Distance: " . $val[1] . "
"; + echo "Message: " . $val[2] . "

"; + endforeach; + else: + echo $value->Value; + endif; + ?> + +
Message;?>Time;?>
+
+
+
+
+

+
+
+
+
Device iotJumpWay Commands
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + retrieveDeviceCommands($Device["context"]["Data"]["id"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + $location = $iotJumpWay->getLocation($value->Location); + $device = $iotJumpWay->getDevice($value->From); + $devicet = $iotJumpWay->getDevice($value->To); + $zone = $iotJumpWay->getZone($value->Zone); + if(!$device["name"]): + $type = "App"; + $application = $iotJumpWay->getApplication($value->From); + $name = $application["name"]; + else: + $type = "Device"; + $name = $device["name"]; + endif; + ?> + + + + + + + + + + + +
IDLocationInfoTime
#_id;?>Location #Location;?>:
+ Zone Zone != 0 ? "#" . $value->Zone . ": " . $zone["zn"] : "NA"; ?>
+ From From != 0 ? "#" . $value->From . ": " . $name : "NA"; ?>
+
+ Type: Type;?>
+ Value: Value;?>
+ Message: Message;?> +
Time;?>
+
+
+
+
+
+
+
+
+
+
+
Online Offline
+
+ +
+  %    +  %    +  %    +  %    +  °C +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+

+
+
+
+
+
+
+
+
+
+
+ +
+

+
+
+
+
+
+
+
+
+ +
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["mqtt"]["username"]); ?>

+
+
+
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["mqtt"]["password"]); ?> +

+
+
+
+
+
+
+
+
+ +
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["amqp"]["username"]) : ""; ?>

+
+
+
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["amqp"]["password"]) : ""; ?> +

Last Updated:

+

+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/AI/GeniSysAI/Media/CSS/GeniSys.css b/Root/var/www/html/AI/GeniSysAI/Media/CSS/GeniSys.css new file mode 100644 index 0000000..3db462d --- /dev/null +++ b/Root/var/www/html/AI/GeniSysAI/Media/CSS/GeniSys.css @@ -0,0 +1,25 @@ +.iotJumpWayText { + font-size: 10px !important; + color: white !important; +} + +.iotJumpWayTextTitle { + font-size: 10px !important; + color: #3b8dda !important; +} + +input:read-only { + background-color: #333 !important; +} + +select:read-only { + background-color: #333 !important; +} + +input:disabled { + background-color: #333 !important; +} + +select:disabled { + background-color: #333 !important; +} \ No newline at end of file diff --git a/Root/var/www/html/GeniSysAI/Media/JS/GeniSysAi.js b/Root/var/www/html/AI/GeniSysAI/Media/JS/GeniSysAi.js similarity index 99% rename from Root/var/www/html/GeniSysAI/Media/JS/GeniSysAi.js rename to Root/var/www/html/AI/GeniSysAI/Media/JS/GeniSysAi.js index 29b7e42..e29d284 100644 --- a/Root/var/www/html/GeniSysAI/Media/JS/GeniSysAi.js +++ b/Root/var/www/html/AI/GeniSysAI/Media/JS/GeniSysAi.js @@ -27,6 +27,7 @@ var GeniSys = { if (submit) { $.post(window.location.href, $("#Login").serialize(), function(arsep) { + console.log(arsep) var arsep = jQuery.parseJSON(arsep); switch (arsep.Response) { case "OK": diff --git a/Root/var/www/html/Blockchain/Media/JS/Logging.js b/Root/var/www/html/AI/GeniSysAI/Media/JS/Logging.js similarity index 100% rename from Root/var/www/html/Blockchain/Media/JS/Logging.js rename to Root/var/www/html/AI/GeniSysAI/Media/JS/Logging.js diff --git a/Root/var/www/html/Blockchain/Media/JS/Validation.js b/Root/var/www/html/AI/GeniSysAI/Media/JS/Validation.js similarity index 100% rename from Root/var/www/html/Blockchain/Media/JS/Validation.js rename to Root/var/www/html/AI/GeniSysAI/Media/JS/Validation.js diff --git a/Root/var/www/html/AI/GeniSysAI/index.php b/Root/var/www/html/AI/GeniSysAI/index.php new file mode 100644 index 0000000..41cc5eb --- /dev/null +++ b/Root/var/www/html/AI/GeniSysAI/index.php @@ -0,0 +1,172 @@ + "AI", + "SubPageID" => "GeniSysAI" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../AI/GeniSysAI/Classes/NLU.php'; + +$_GeniSysAi->checkSession(); +$Devices = $NLU->getDevices(); + +?> + + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
GeniSysAI Natural Language Understanding Models
+
+
+
+
+
+
+

GeniSysAI Natural Language Understanding Models are a range of Natural Language Understanding models for creating AI Assistants designed to be used on constrained devices making them suitable for IoT networks. These models use a variety of programming languages, frameworks and hardware providing. You can download our GeniSysAI models from the HIAS GeniSysAI repository. Instructions for installation are provided in the tutorials.

+
+
+
+
+
+
+
GeniSysAI NLU Devices
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + $value): + ?> + + + + + + + + + + + +
IDDETAILSSTATUSACTION
# + Name:
+ Zone: # +
+
"> + +
+
">
+
+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/AI/Model.php b/Root/var/www/html/AI/Model.php new file mode 100644 index 0000000..4d276ea --- /dev/null +++ b/Root/var/www/html/AI/Model.php @@ -0,0 +1,382 @@ + "AI", + "SubPageID" => "Models" +]; + +include dirname(__FILE__) . '/../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../AI/Classes/AI.php'; + +$_GeniSysAi->checkSession(); + +$mid = filter_input(INPUT_GET, 'model', FILTER_SANITIZE_NUMBER_INT); +$model = $AI->getModel($mid); + +?> + + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
AI Classifier Model #
+
+
+
+
+
+
+
+
+
+
+
+ + "> + Name of Model +
+
+ + "> + Description of model +
+
+ + "> + Link to model +
+
+ + + Category of AI model +
+
+ + + Type of AI network +
+
+ + + Type of AI network +
+
+ + + Programming language used to develop the model +
+
+ + + Framework used to develop the model +
+
+ + + Toolkit used to develop the model +
+
+ + "> + Dataset used to train and test model +
+
+ + "> + Dataset author +
+
+ + "> + Dataset link +
+
+ + > + Dataset Augmentation +
+
+ + + Dataset type +
+
+ + "> + To be used in the HIAS UI, test data is expected to include the label at the end of the file name. Specify the positive label here. +
+
+ + "> + To be used in the HIAS UI, test data is expected to include the label at the end of the file name. Specify the negative label here. +
+
+ + +
+
+
+
+ + "> + Author(s) +
+
+ + "> + Author Link +
+
+ + "> + Related research paper +
+
+ + "> + Related research paper author +
+
+ + "> + Related research paper DOI +
+
+ + "> + Related research paper link +
+
+ +
+ $value): + if($key != "image"): + ?> + +
+
+ +
+
+ +
+
+ + +
+ Model Properties +
+
+ +
+ $value): + if(is_array($value)): + $value = implode(',',$value); + endif; + ?> + +
+
+ + +
+
+
+
+
+ + + +
+ Model Commands +
+
+ +
+ + $value): + ?> + +
+
+ +
+
+ +
+
+ + + + +
+ Model States +
+
+
+
+
+
+
+

+
+
+
+
Model Schema
+
+
+
+
+
+
+
+ "; ?> "; ?> +
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/AI/Skin/Classes/Skin.js b/Root/var/www/html/AI/Skin/Classes/Skin.js new file mode 100644 index 0000000..e71b100 --- /dev/null +++ b/Root/var/www/html/AI/Skin/Classes/Skin.js @@ -0,0 +1,176 @@ +var Skin = { + Create: function() { + $.post(window.location.href, $("#skin_classifier").serialize(), function(resp) { + console.log(resp); + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + GeniSys.ResetForm("skin_classifier"); + $('.modal-title').text('Skin Classifier Devices'); + $('.modal-body').html("HIAS Skin Classifier Device ID #" + resp.GDID + " created! Please save the API keys safely. The device's credentials are provided below. The credentials can be reset in the GeniSyAI Security Devices area.

Device ID: " + resp.DID + "
MQTT User: " + resp.MU + "
MQTT Password: " + resp.MP + "

Blockchain User: " + resp.BU + "
Blockchain Pass: " + resp.BP + "

App ID: " + resp.AppID + "
App Key: " + resp.AppKey + "

" + resp.Message); + $('#responsive-modal').modal('show'); + Logging.logMessage("Core", "Forms", "Device ID #" + resp.DID + " created!"); + break; + default: + msg = "Skin Create Failed: " + resp.Message + Logging.logMessage("Core", "Skin", msg); + $('.modal-title').text('Skin Classifier Devices'); + $('.modal-body').text(msg); + $('#responsive-modal').modal('show'); + break; + } + }); + }, + Update: function() { + $.post(window.location.href, $("#skin_classifier_update").serialize(), function(resp) { + console.log(resp) + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + var fjson = JSON.stringify(resp.Schema, null, '\t'); + window.parent.$('#schema').html(fjson); + Logging.logMessage("Core", "Forms", "Device Update OK"); + $('.modal-title').text('Skin Classifier Devices'); + $('.modal-body').text(resp.Message); + $('#responsive-modal').modal('show'); + break; + default: + msg = "Skin Update Failed: " + resp.Message + Logging.logMessage("Core", "Skin", msg); + $('.modal-title').text('Skin Classifier Devices'); + $('.modal-body').text(msg); + $('#responsive-modal').modal('show'); + break; + } + }); + }, + deleteData: function() { + $.post(window.location.href, { "deleteData": 1 }, function(resp) { + console.log(resp) + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + $('#dataBlock').empty(); + $('#dataBlock').html("

Please upload your test dataset.

"); + break; + default: + break; + } + }); + }, + prepareUploadForm: function() { + + var upper = document.querySelector('#dataup'), + form = new FormData(), + xhr = new XMLHttpRequest(); + + form.append('uploadAllData', 1); + + upper.addEventListener('change', function(event) { + event.preventDefault(); + + var files = this.files; + for (var i = 0, n = files.length; i < n; i++) { + var file = files[i]; + + form.append('skindata[]', file, file.name); + + xhr.onload = function() { + if (xhr.status === 200) { + var resp = jQuery.parseJSON(xhr.response); + if (resp.Response === "OK") { + $('#dataBlock').empty(); + $('#dataBlock').html(resp.Data); + $('.modal-title').text('Data Upload OK'); + $('.modal-body').text(resp.Message); + $('#responsive-modal').modal('show'); + Skin.setOpacity(); + Logging.logMessage("Core", "Forms", resp.Message); + } else { + Logging.logMessage("Core", "Forms", resp.Message); + $('.modal-title').text('Data Upload Failed'); + $('.modal-body').text(resp.Message); + $('#responsive-modal').modal('show'); + } + } + } + + xhr.open('POST', ''); + xhr.send(form); + } + }); + }, + setOpacity: function() { + $('.classify').css("opacity", "1.0"); + $('.classify').hover(function() { + $(this).stop().animate({ opacity: 0.2 }, "fast"); + }, + function() { + $(this).stop().animate({ opacity: 1.0 }, "fast"); + }); + }, + classify: function(im) { + + $('#imageView').html(""); + $("#imName").text(im); + var classification = ''; + $("#imClass").html("Diagnosis: WAITING FOR RESPONSE"); + $("#imResult").html("Result: WAITING FOR RESPONSE"); + $.post(window.location.href, { "classifyData": 1, "im": im }, function(resp) { + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + console.log(im); + console.log(im.includes("_0")); + console.log(resp.Diagnosis); + if (im.includes("_0") && resp.Diagnosis == "Negative") { + classification = "True Negative"; + } else if (im.includes("_0") && resp.Diagnosis == "Positive") { + classification = "False Positive"; + } else if (im.includes("_1") && resp.Diagnosis == "Positive") { + classification = "True Positive"; + } else if (im.includes("_1") && resp.Diagnosis == "Negative") { + classification = "False Negative"; + } + $("#imClass").html("Diagnosis: " + resp.Diagnosis); + $("#imResult").html("Result: " + classification); + break; + default: + break; + } + }); + + } +}; +$(document).ready(function() { + + $('#skin_classifier').validator().on('submit', function(e) { + if (!e.isDefaultPrevented()) { + e.preventDefault(); + Skin.Create(); + } + }); + + $('#skin_classifier_update').validator().on('submit', function(e) { + if (!e.isDefaultPrevented()) { + e.preventDefault(); + Skin.Update(); + } + }); + + $("#GeniSysAI").on("click", "#uploadData", function(e) { + e.preventDefault(); + $('#dataup').trigger('click'); + }); + + $("#GeniSysAI").on("click", "#deleteData", function(e) { + e.preventDefault(); + Skin.deleteData(); + }); + + $("#GeniSysAI").on("click", ".classify", function(e) { + e.preventDefault(); + Skin.classify($(this).attr("id")); + }); + +}); \ No newline at end of file diff --git a/Root/var/www/html/AI/Skin/Classes/Skin.php b/Root/var/www/html/AI/Skin/Classes/Skin.php new file mode 100644 index 0000000..46eaad3 --- /dev/null +++ b/Root/var/www/html/AI/Skin/Classes/Skin.php @@ -0,0 +1,1687 @@ +_GeniSys = $_GeniSys; + $this->bcc = $this->getBlockchainConf(); + $this->web3 = $this->blockchainConnection(); + $this->contract = new Contract($this->web3->provider, $this->bcc["abi"]); + $this->icontract = new Contract($this->web3->provider, $this->bcc["iabi"]); + $this->checkBlockchainPermissions(); + endif; + $this->cb = $this->getContextBrokerConf(); + } + + public function setClassifierConfs() + { + $TId = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_NUMBER_INT); + $Device = $this->getDevice($TId); + + $this->dataDir = "Data/" . $Device["context"]["Data"]["dataset"]["folder"] . "/"; + $this->dataDirFull = "/fserver/var/www/html/AI/Skin//"; + $this->dataFiles = $this->dataDir . "*.jpg"; + $this->allowedFiles = ["jpg","JPG"]; + $this->api = $this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"])."/AI/Skin/" . $Device["context"]["Data"]["proxy"]["endpoint"] . "/Inference"; + } + + public function getContextBrokerConf() + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM contextbroker + "); + $pdoQuery->execute(); + $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + return $response; + } + + private function createContextHeaders() + { + $basicAuth = $_SESSION["GeniSysAI"]["User"] . ":" . $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"]); + $basicAuth = base64_encode($basicAuth); + + return [ + "Content-Type: application/json", + 'Authorization: Basic '. $basicAuth + ]; + } + + private function contextBrokerRequest($method, $endpoint, $headers, $json) + { + $path = $this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/" . $this->cb["url"] . "/" . $endpoint; + + if($method == "GET"): + $ch = curl_init(); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_URL, $path); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "POST"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "PATCH"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + endif; + + return $body; + } + + public function getBlockchainConf() + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT blockchain.*, + contracts.contract, + contracts.abi, + icontracts.contract as icontract, + icontracts.abi as iabi + FROM blockchain blockchain + INNER JOIN contracts contracts + ON contracts.id = blockchain.dc + INNER JOIN contracts icontracts + ON icontracts.id = blockchain.ic + "); + $pdoQuery->execute(); + $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + return $response; + } + + private function blockchainConnection() + { + if(isSet($_SESSION["GeniSysAI"]["Active"])): + $web3 = new Web3($this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/Blockchain/API/", 30, $_SESSION["GeniSysAI"]["User"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"])); + return $web3; + endif; + } + + private function checkBlockchainPermissions() + { + $allowed = ""; + $errr = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->call("identifierAllowed", "User", $_SESSION["GeniSysAI"]["Identifier"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$allowed, &$errr) { + if ($err !== null) { + $allowed = "FAILED"; + $errr = $err; + return; + } + $allowed = $resp; + }); + if(!$allowed): + header('Location: /Logout'); + endif; + } + + private function unlockBlockchainAccount() + { + $response = ""; + $personal = $this->web3->personal; + $personal->unlockAccount($_SESSION["GeniSysAI"]["BC"]["BCUser"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["BC"]["BCPass"]), function ($err, $unlocked) use (&$response) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + if ($unlocked) { + $response = "OK"; + } else { + $response = "FAILED"; + } + }); + + return $response; + } + + private function lockBlockchainAccount() + { + $response = ""; + $personal = $this->web3->personal; + $personal->lockAccount($_SESSION["GeniSysAI"]["BC"]["BCUser"], function ($err, $unlocked) use (&$response) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + if ($unlocked) { + $response = "OK"; + } else { + $response = "FAILED"; + } + }); + + return $response; + } + + private function createBlockchainUser($pass) + { + $newAccount = ""; + $personal = $this->web3->personal; + $personal->newAccount($pass, function ($err, $account) use (&$newAccount) { + if ($err !== null) { + $newAccount = "FAILED!"; + return; + } + $newAccount = $account; + }); + + return $newAccount; + } + + private function getBlockchainBalance() + { + $nbalance = ""; + $this->web3->eth->getBalance($_SESSION["GeniSysAI"]["BC"]["BCUser"], function ($err, $balance) use (&$nbalance) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + $nbalance = $balance->toString(); + }); + + return Utils::fromWei($nbalance, 'ether')[0]; + } + + private function addAmqpUser($username, $key) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpu ( + `username`, + `pw` + ) VALUES ( + :username, + :pw + ) + "); + $query->execute([ + ':username' => $username, + ':pw' => $this->_GeniSys->_helpers->oEncrypt($key) + ]); + $amid = $this->_GeniSys->_secCon->lastInsertId(); + return $amid; + } + + private function addAmqpUserVh($uid, $vhost) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvh ( + `uid`, + `vhost` + ) VALUES ( + :uid, + :vhost + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost + ]); + } + + private function addAmqpVhPerm($uid, $vhost, $rtype, $rname, $permission) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvhr ( + `uid`, + `vhost`, + `rtype`, + `rname`, + `permission` + ) VALUES ( + :uid, + :vhost, + :rtype, + :rname, + :permission + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost, + ':rtype' => $rtype, + ':rname' => $rname, + ':permission' => $permission + ]); + } + + private function addAmqpVhTopic($uid, $vhost, $rtype, $rname, $permission, $rkey) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvhrt ( + `uid`, + `vhost`, + `rtype`, + `rname`, + `permission`, + `rkey` + ) VALUES ( + :uid, + :vhost, + :rtype, + :rname, + :permission, + :rkey + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost, + ':rtype' => $rtype, + ':rname' => $rname, + ':permission' => $permission, + ':rkey' => $rkey + ]); + } + + private function storeBlockchainTransaction($action, $hash, $device = 0, $application = 0) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + INSERT INTO transactions ( + `uid`, + `did`, + `aid`, + `action`, + `hash`, + `time` + ) VALUES ( + :uid, + :did, + :aid, + :action, + :hash, + :time + ) + "); + $pdoQuery->execute([ + ":uid" => $_SESSION["GeniSysAI"]["Uid"], + ":did" => $device, + ":aid" => $application, + ":action" => $action, + ':hash' => $this->_GeniSys->_helpers->oEncrypt($hash), + ":time" => time() + ]); + $txid = $this->_GeniSys->_secCon->lastInsertId(); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + return $txid; + } + + private function storeUserHistory($action, $hash, $location = 0, $zone = 0, $device = 0, $sensor = 0, $application = 0) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + INSERT INTO history ( + `uid`, + `tlid`, + `tzid`, + `tdid`, + `tsid`, + `taid`, + `action`, + `hash`, + `time` + ) VALUES ( + :uid, + :tlid, + :tzid, + :tdid, + :tsid, + :taid, + :action, + :hash, + :time + ) + "); + $pdoQuery->execute([ + ":uid" => $_SESSION["GeniSysAI"]["Uid"], + ":tlid" => $location, + ":tzid" => $zone, + ":tdid" => $device, + ":tsid" => $sensor, + ":taid" => $application, + ":action" => $action, + ":hash" => $hash, + ":time" => time() + ]); + $txid = $this->_GeniSys->_secCon->lastInsertId(); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + return $txid; + } + + public function checkLocation($lid) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT id + FROM mqttl + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $lid + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($location["id"]): + return True; + else: + return False; + endif; + } + + public function getLocation($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttl + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $location["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $location["pub"] . "?type=Location" . $attrs, $this->createContextHeaders(), []), true); + return $location; + } + + public function checkZone($zid) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT id + FROM mqttlz + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $zid + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($location["id"]): + return True; + else: + return False; + endif; + } + + public function getZone($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttlz + WHERE id = :id + ORDER BY id DESC + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $zone=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $zone["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $zone["pub"] . "?type=Zone" . $attrs, $this->createContextHeaders(), []), true); + return $zone; + } + + public function getDevices($limit = 0) + { + $limiter = ""; + if($limit != 0): + $limiter = "&limit=" . $limit; + endif; + + $devices = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "?type=Device&category=SkinCancerClassifier".$limiter, $this->createContextHeaders(), []), true); + return $devices; + } + + public function getDevice($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttld + WHERE id = :id + ORDER BY id DESC + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $device=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $device["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $device["apub"] . "?type=Device" . $attrs, $this->createContextHeaders(), []), true); + return $device; + } + + public function getThing($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM things + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $thing=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $thing["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $thing["pub"] . "?type=Thing" . $attrs, $this->createContextHeaders(), []), true); + return $thing; + } + + public function getModel($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM models + WHERE id = :id + ORDER BY id DESC + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $model=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $device["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $model["pub"] . "?type=Model" . $attrs, $this->createContextHeaders(), []), true); + return $device; + } + + public function createDevice() + { + if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Location ID is required" + ]; + endif; + + if(!$this->checkLocation(filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Zone ID is required" + ]; + endif; + + if(!$this->checkZone(filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay zone does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Category is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ctype", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Model type is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device model is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system version is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Coordinates entity is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IP is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "MAC is required" + ]; + endif; + + if(!count($_POST["protocols"])): + return [ + "Response"=> "Failed", + "Message" => "At least one M2M protocol is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetUsed", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset used is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetLink", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset link is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetAuthor", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset author is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset folder is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "relatedPaper", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Related paper is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Device server port is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "endpoint", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Proxy endpoint is required" + ]; + endif; + + $unlocked = $this->unlockBlockchainAccount(); + + if($unlocked == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Unlocking HIAS Blockhain Account Failed!" + ]; + endif; + + $mqttUser = $this->_GeniSys->_helpers->generate_uuid(); + $mqttPass = $this->_GeniSys->_helpers->password(); + $mqttHash = create_hash($mqttPass); + + $pubKey = $this->_GeniSys->_helpers->generate_uuid(); + $privKey = $this->_GeniSys->_helpers->generateKey(32); + $privKeyHash = $this->_GeniSys->_helpers->createPasswordHash($privKey); + + $amqppubKey = $this->_GeniSys->_helpers->generate_uuid(); + $amqpprvKey = $this->_GeniSys->_helpers->generateKey(32); + $amqpKeyHash = $this->_GeniSys->_helpers->createPasswordHash($amqpprvKey); + + $bcPass = $this->_GeniSys->_helpers->password(); + + $lid = filter_input(INPUT_POST, 'lid', FILTER_SANITIZE_NUMBER_INT); + $location = $this->getLocation($lid); + + $zid = filter_input(INPUT_POST, 'zid', FILTER_SANITIZE_NUMBER_INT); + $zone = $this->getZone($zid); + + $ip = filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING); + $mac = filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING); + $bluetooth = filter_input(INPUT_POST, "bluetooth", FILTER_SANITIZE_STRING); + $name = filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING); + $coords = explode(",", filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)); + + mkdir("Data/" . filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING), 0777, true); + + $protocols = []; + foreach($_POST["protocols"] AS $key => $value): + $protocols[] = $value; + endforeach; + + $models = []; + if(isSet($_POST["ai"])): + foreach($_POST["ai"] AS $key => $value): + $model = $this->getModel($value)["context"]["Data"]; + $mname = $model["name"]["value"]; + unset($model["id"]); + unset($model["type"]); + unset($model["mid"]); + unset($model["name"]); + unset($model["description"]); + unset($model["network"]); + unset($model["language"]); + unset($model["framework"]); + unset($model["toolkit"]); + unset($model["dateCreated"]); + unset($model["dateModified"]); + $models[$mname] = $model; + endforeach; + endif; + + $sensors = []; + if(isSet($_POST["sensors"])): + foreach($_POST["sensors"] AS $key => $value): + $sensor = $this->getThing($value)["context"]["Data"]; + unset($sensor["id"]); + unset($sensor["type"]); + unset($sensor["category"]); + unset($sensor["description"]); + unset($sensor["thing"]); + unset($sensor["properties"]["image"]); + unset($sensor["dateCreated"]); + unset($sensor["dateModified"]); + $sensors[] = $sensor; + endforeach; + endif; + + $actuators = []; + if(isSet($_POST["actuators"])): + foreach($_POST["actuators"] AS $key => $value): + $actuator = $this->getThing($value)["context"]["Data"]; + unset($actuator["id"]); + unset($actuator["type"]); + unset($actuator["category"]); + unset($actuator["description"]); + unset($actuator["thing"]); + unset($actuator["properties"]["image"]); + unset($actuator["dateCreated"]); + unset($actuator["dateModeified"]); + $actuators[] = $actuator; + endforeach; + endif; + + $newBcUser = $this->createBlockchainUser($bcPass); + + if($newBcUser == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Creating New HIAS Blockhain Account Failed!" + ]; + endif; + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttld ( + `apub` + ) VALUES ( + :apub + ) + "); + $query->execute([ + ':apub' => $pubKey + ]); + $did = $this->_GeniSys->_secCon->lastInsertId(); + + $data = [ + "id" => $pubKey, + "type" => "Device", + "category" => [ + "value" => [filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)] + ], + "name" => [ + "value" => $name + ], + "description" => [ + "value" => filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING) + ], + "lid" => [ + "value" => $lid, + "entity" => $location["context"]["Data"]["id"] + ], + "zid" => [ + "value" => $zid, + "entity" => $zone["context"]["Data"]["id"] + ], + "did" => [ + "value" => $did + ], + "location" => [ + "type" => "geo:json", + "value" => [ + "type" => "Point", + "coordinates" => [floatval($coords[0]), floatval($coords[1])] + ] + ], + "agent" => [ + "url" => filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING) + ], + "paper" => [ + "title" => filter_input(INPUT_POST, "relatedPaper", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "relatedPaperAuthor", FILTER_SANITIZE_STRING), + "doi" => filter_input(INPUT_POST, "relatedPaperDOI", FILTER_SANITIZE_STRING), + "link" => filter_input(INPUT_POST, "relatedPaperLink", FILTER_SANITIZE_STRING) + ], + "dataset" => [ + "name" => filter_input(INPUT_POST, "datasetUsed", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "datasetAuthor", FILTER_SANITIZE_STRING), + "url" => filter_input(INPUT_POST, "datasetLink", FILTER_SANITIZE_STRING), + "folder" => filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING) + ], + "device" => [ + "type" => filter_input(INPUT_POST, "ctype", FILTER_SANITIZE_STRING), + "name" => filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING), + "model" => filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING) + ], + "proxy" => [ + "endpoint" => filter_input(INPUT_POST, "endpoint", FILTER_SANITIZE_STRING) + ], + "stream" => [ + "port" => filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING), + "file" => "" + ], + "socket" => [ + "port" => "" + ], + "os" => [ + "name" => filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING), + "version" => filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING) + ], + "protocols" => $protocols, + "status" => [ + "value" => "OFFLINE", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "keys" => [ + "public" => $pubKey, + "private" => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "blockchain" => [ + "address" => $newBcUser, + "password" => $this->_GeniSys->_helpers->oEncrypt($bcPass) + ], + "mqtt" => [ + "username" => $this->_GeniSys->_helpers->oEncrypt($mqttUser), + "password" => $this->_GeniSys->_helpers->oEncrypt($mqttPass), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "coap" => [ + "username" => "", + "password" => "" + ], + "amqp" => [ + "username" => $this->_GeniSys->_helpers->oEncrypt($amqppubKey), + "password" => $this->_GeniSys->_helpers->oEncrypt($amqpprvKey), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "batteryLevel" => [ + "value" => 0.00 + ], + "cpuUsage" => [ + "value" => 0.00 + ], + "memoryUsage" => [ + "value" => 0.00 + ], + "hddUsage" => [ + "value" => 0.00 + ], + "temperature" => [ + "value" => 0.00 + ], + "ip" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($ip), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "mac" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($mac), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "bluetooth" => [ + "address" => $bluetooth ? $this->_GeniSys->_helpers->oEncrypt($bluetooth) : "", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "ai" => $models, + "sensors" => $sensors, + "actuators" => $actuators, + "dateCreated" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateFirstUsed" => [ + "type" => "DateTime", + "value" => "" + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + + $response = json_decode($this->contextBrokerRequest("POST", $this->cb["entities_url"] . "?type=Device", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttu ( + `lid`, + `zid`, + `did`, + `uname`, + `pw` + ) VALUES ( + :lid, + :zid, + :did, + :uname, + :pw + ) + "); + $query->execute([ + ':lid' => $lid, + ':zid' => $zid, + ':did' => $did, + ':uname' => $mqttUser, + ':pw' => $mqttHash + ]); + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttua ( + `lid`, + `zid`, + `did`, + `username`, + `topic`, + `rw` + ) VALUES ( + :lid, + :zid, + :did, + :username, + :topic, + :rw + ) + "); + $query->execute(array( + ':lid' => $lid, + ':zid' => $zid, + ':did' => $did, + ':username' => $mqttUser, + ':topic' => $location["context"]["Data"]["id"] . "/Devices/" . $zone["context"]["Data"]["id"] . "/" . $pubKey . "/#", + ':rw' => 4 + )); + + $amid = $this->addAmqpUser($amqppubKey, $amqpKeyHash); + $this->addAmqpUserVh($amid, "iotJumpWay"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "exchange", "Core", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "exchange", "Core", "write"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Life", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Life", "write"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Statuses", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Statuses", "write"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "read", "Life"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "write", "Life"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "read", "Statuses"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "write", "Statuses"); + + $hash = ""; + $msg = ""; + $actionMsg = ""; + $balanceMessage = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("registerDevice", $pubKey, $newBcUser, $lid, $zid, $did, $name, $_SESSION["GeniSysAI"]["Uid"], time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg = " HIAS Blockchain registerDevice failed!\n" . $msg; + else: + $txid = $this->storeBlockchainTransaction("Register Device", $hash, $did); + $this->storeUserHistory("Register Device", $txid, $lid, $zid, $did); + $balance = $this->getBlockchainBalance(); + $actionMsg = " HIAS Blockchain registerDevice OK!\n"; + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; + endif; + + $this->icontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["icontract"]))->send("registerAuthorized", $newBcUser, ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg .= " HIAS Blockchain registerAuthorized failed! " . $msg; + else: + $txid = $this->storeBlockchainTransaction("iotJumpWay Register Authorized", $hash, $did); + $this->storeUserHistory("Register Authorized", $txid, $lid, $zid, $did); + $balance = $this->getBlockchainBalance(); + $actionMsg .= " HIAS Blockchain registerAuthorized OK!\n"; + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; + endif; + + return [ + "Response"=> "OK", + "Message" => "Device created!" . $actionMsg . $balanceMessage, + "LID" => filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT), + "ZID" => filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT), + "DID" => $did, + "MU" => $mqttUser, + "MP" => $mqttPass, + "BU" => $newBcUser, + "BP" => $bcPass, + "AppID" => $pubKey, + "AppKey" => $privKey + ]; + else: + return [ + "Response"=> "FAILED", + "Message" => "Device creating failed" + ]; + endif; + } + + public function updateDevice() + { + if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Location ID is required" + ]; + endif; + + if(!$this->checkLocation(filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Zone ID is required" + ]; + endif; + + if(!$this->checkZone(filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay zone does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Category is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ctype", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Model type is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device model is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system version is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Coordinates entity is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IP is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "MAC is required" + ]; + endif; + + if(!count($_POST["protocols"])): + return [ + "Response"=> "Failed", + "Message" => "At least one M2M protocol is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetUsed", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset used is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetLink", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset link is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetAuthor", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset author is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Dataset folder is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "relatedPaper", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Related paper is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Device server port is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "endpoint", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Proxy endpoint is required" + ]; + endif; + + $unlocked = $this->unlockBlockchainAccount(); + + if($unlocked == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Unlocking HIAS Blockhain Account Failed!" + ]; + endif; + + $ip = filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING); + $mac = filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING); + $bluetooth = filter_input(INPUT_POST, "bluetooth", FILTER_SANITIZE_STRING); + $name = filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING); + $status = filter_input(INPUT_POST, "status", FILTER_SANITIZE_STRING); + $coords = explode(",", filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)); + + $did = filter_input(INPUT_GET, "device", FILTER_SANITIZE_NUMBER_INT); + $device = $this->getDevice($did); + + $lid = filter_input(INPUT_POST, 'lid', FILTER_SANITIZE_NUMBER_INT); + $location = $this->getLocation($lid); + + $zid = filter_input(INPUT_POST, 'zid', FILTER_SANITIZE_NUMBER_INT); + $zone = $this->getZone($zid); + + $protocols = []; + foreach($_POST["protocols"] AS $key => $value): + $protocols[] = $value; + endforeach; + + $models = []; + if(isSet($_POST["ai"])): + foreach($_POST["ai"] AS $key => $value): + $model = $this->getModel($value)["context"]["Data"]; + $mname = $model["name"]["value"]; + unset($model["id"]); + unset($model["type"]); + unset($model["mid"]); + unset($model["name"]); + unset($model["description"]); + unset($model["network"]); + unset($model["language"]); + unset($model["framework"]); + unset($model["toolkit"]); + unset($model["dateCreated"]); + unset($model["dateModified"]); + $models[$mname] = $model; + endforeach; + endif; + + $sensors = []; + if(isSet($_POST["sensors"])): + foreach($_POST["sensors"] AS $key => $value): + $sensor = $this->getThing($value)["context"]["Data"]; + unset($sensor["id"]); + unset($sensor["type"]); + unset($sensor["category"]); + unset($sensor["description"]); + unset($sensor["thing"]); + unset($sensor["properties"]["image"]); + unset($sensor["dateCreated"]); + unset($sensor["dateModified"]); + $sensors[] = $sensor; + endforeach; + endif; + + $actuators = []; + if(isSet($_POST["actuators"])): + foreach($_POST["actuators"] AS $key => $value): + $actuator = $this->getThing($value)["context"]["Data"]; + unset($actuator["id"]); + unset($actuator["type"]); + unset($actuator["category"]); + unset($actuator["description"]); + unset($actuator["thing"]); + unset($actuator["properties"]["image"]); + unset($actuator["dateCreated"]); + unset($actuator["dateModeified"]); + $actuators[] = $actuator; + endforeach; + endif; + + if($device["context"]["Data"]["lid"]["value"] != $lid): + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttu + SET lid = :lid + WHERE did = :did + "); + $query->execute([ + ':lid' => $lid, + ':did' => $did + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttua + SET lid = :lid + WHERE did = :did + "); + $query->execute([ + ':lid' => $lid, + ':did' => $did + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttua + SET topic = :topicN + WHERE did = :did + & topic = :topic + "); + $query->execute([ + ':topicN' => $device["context"]["Data"]["lid"]["entity"] . "/ " . $device["context"]["Data"]["zid"]["entity"] . "/Devices/" . $device["context"]["Data"]["id"] . "/#", + ':did' => $did, + ':topic' => $location["context"]["Data"]["id"] . "/Devices/" . $zone["context"]["Data"]["id"] . "/Devices/" . $device["context"]["Data"]["id"] . "/#" + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + endif; + + $data = [ + "category" => [ + "value" => [filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)] + ], + "name" => [ + "value" => $name + ], + "description" => [ + "value" => filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING) + ], + "lid" => [ + "value" => $lid, + "entity" => $location["context"]["Data"]["id"] + ], + "zid" => [ + "value" => $zid, + "entity" => $zone["context"]["Data"]["id"] + ], + "location" => [ + "type" => "geo:json", + "value" => [ + "type" => "Point", + "coordinates" => [floatval($coords[0]), floatval($coords[1])] + ] + ], + "agent" => [ + "url" => filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING) + ], + "paper" => [ + "title" => filter_input(INPUT_POST, "relatedPaper", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "relatedPaperAuthor", FILTER_SANITIZE_STRING), + "doi" => filter_input(INPUT_POST, "relatedPaperDOI", FILTER_SANITIZE_STRING), + "link" => filter_input(INPUT_POST, "relatedPaperLink", FILTER_SANITIZE_STRING) + ], + "dataset" => [ + "name" => filter_input(INPUT_POST, "datasetUsed", FILTER_SANITIZE_STRING), + "author" => filter_input(INPUT_POST, "datasetAuthor", FILTER_SANITIZE_STRING), + "url" => filter_input(INPUT_POST, "datasetLink", FILTER_SANITIZE_STRING), + "folder" => filter_input(INPUT_POST, "datasetFolder", FILTER_SANITIZE_STRING) + ], + "device" => [ + "type" => filter_input(INPUT_POST, "ctype", FILTER_SANITIZE_STRING), + "name" => filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING), + "model" => filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING) + ], + "proxy" => [ + "endpoint" => filter_input(INPUT_POST, "endpoint", FILTER_SANITIZE_STRING) + ], + "stream" => [ + "port" => filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING), + "file" => "" + ], + "socket" => [ + "port" => "" + ], + "os" => [ + "name" => filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING), + "version" => filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING) + ], + "protocols" => $protocols, + "ip" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($ip), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "mac" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($mac), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "bluetooth" => [ + "address" => $bluetooth ? $this->_GeniSys->_helpers->oEncrypt($bluetooth) : "", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "ai" => $models, + "sensors" => $sensors, + "actuators" => $actuators, + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $device["context"]["Data"]["id"] . "/attrs?type=Device", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + + $hash = ""; + $msg = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("updateDevice", $device["context"]["Data"]["id"], "Device", $lid, $zid, $did, $name, $device["context"]["Data"]["status"]["value"], time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + $balance = ""; + $balanceMessage = ""; + $actionMsg = ""; + if($hash == "FAILED"): + $actionMsg = " HIAS Blockchain updateDevice failed! " . $msg; + else: + $txid = $this->storeBlockchainTransaction("Update Device", $hash, $did); + $this->storeUserHistory("Updated Device", $txid, $lid, $zid, $did); + $balance = $this->getBlockchainBalance(); + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; + endif; + + $device = $this->getDevice($did); + + return [ + "Response"=> "OK", + "Message" => "Device updated!" . $actionMsg . $balanceMessage, + "Schema" => $device["context"]["Data"] + ]; + else: + return [ + "Response"=> "Failed", + "Message" => "There was a problem updating this device context data!" + ]; + endif; + } + + public function deleteData() + { + $images = glob($this->dataFiles); + foreach( $images as $image ): + unlink($image); + endforeach; + + return [ + "Response" => "OK", + "Message" => "Deleted Acute Lymphoblastic Leukemia Image Database for Image Processing Dataset" + ]; + + } + + public function uploadData() + { + $dataCells = ''; + if(is_array($_FILES) && !empty($_FILES['skindata'])): + foreach($_FILES['skindata']['name'] as $key => $filename): + $file_name = explode(".", $filename); + if(in_array($file_name[1], $this->allowedFiles)): + $sourcePath = $_FILES["skindata"]["tmp_name"][$key]; + $targetPath = $this->dataDir . $filename; + if(!move_uploaded_file($sourcePath, $targetPath)): + return [ + "Response" => "FAILED", + "Message" => "Upload failed " . $targetPath + ]; + endif; + else: + return [ + "Response" => "FAILED", + "Message" => "Please upload jpg files" + ]; + endif; + endforeach; + + $images = glob($this->dataFiles); + $count = 1; + foreach($images as $image): + $dataCells .= "
"; + if($count%6 == 0): + $dataCells .= "
"; + endif; + $count++; + endforeach; + + else: + return [ + "Response" => "FAILED", + "Message" => "You must upload some images (jpg)" + ]; + endif; + + return [ + "Response" => "OK", + "Message" => "Data upload OK!", + "Data" => $dataCells + ]; + + } + + public function classifyData() + { + $file = $this->dataDirFull . filter_input(INPUT_POST, "im", FILTER_SANITIZE_STRING); + $mime = mime_content_type($file); + $info = pathinfo($file); + $name = $info['basename']; + $toSend = new CURLFile($file, $mime, $name); + + $headers = [ + 'Authorization: Basic '. base64_encode($_SESSION["GeniSysAI"]["User"] . ":" . $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"])) + ]; + + $ch = curl_init($this->api); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_POSTFIELDS, [ + 'file'=> $toSend, + ]); + + $resp = curl_exec($ch); + + return json_decode($resp, true); + + } + + } + + $Skin = new Skin($_GeniSys); + + if(filter_input(INPUT_POST, "create_skin_classifier", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($Skin->createDevice())); + endif; + + if(filter_input(INPUT_POST, "update_skin_classifier", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($Skin->updateDevice())); + endif; + + if(filter_input(INPUT_POST, "reset_mqtt", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($Skin->resetMqtt())); + endif; + + if(filter_input(INPUT_POST, "reset_key", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($Skin->resetDvcKey())); + endif; + + if(filter_input(INPUT_POST, "get_tlife", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($Skin->getLife())); + endif; + + if(filter_input(INPUT_POST, "deleteData", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($Skin->deleteData())); + endif; + + if(filter_input(INPUT_POST, "uploadAllData", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($Skin->uploadData())); + endif; + + if(filter_input(INPUT_POST, "classifyData", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($Skin->classifyData())); + endif; diff --git a/Root/var/www/html/AI/Skin/Classify.php b/Root/var/www/html/AI/Skin/Classify.php new file mode 100644 index 0000000..406ab26 --- /dev/null +++ b/Root/var/www/html/AI/Skin/Classify.php @@ -0,0 +1,202 @@ + "AI", + "SubPageID" => "AISkin" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/Skin/Classes/Skin.php'; + +$_GeniSysAi->checkSession(); + +$Locations = $iotJumpWay->getLocations(); +$Zones = $iotJumpWay->getZones(); +$Devices = $Skin->getDevices(); + +$TId = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_NUMBER_INT); +$Device = $iotJumpWay->getDevice($TId); + +list($dev1On, $dev1Off) = $iotJumpWay->getStatusShow($Device["context"]["Data"]["status"]["value"]); + +$Skin->setClassifierConfs(); + +?> + + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
Skin Classifier #
+
+ +
+
+
+
+ + + +
+ + dataFiles); + $count = 1; + if(count($images)): + foreach( $images as $image ): + echo "
"; + if($count%6 == 0): + echo"
"; + endif; + $count++; + endforeach; + else: + echo "

Please upload your test dataset.

"; + endif; + ?> + +
+
+
+
+
+
+
+
+
+
Online Offline
+
+ +
+  %    +  %    +  %    +  %    +  °C +
+
+
+
+
+
+
+
+
Diagnosis Results
+
+
+
+
+
+
+
+ +
+
+

+
+
+ +
+ +
+
+
+
+
+ +
+ + + +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/AI/Skin/Create.php b/Root/var/www/html/AI/Skin/Create.php new file mode 100644 index 0000000..f6c6c6e --- /dev/null +++ b/Root/var/www/html/AI/Skin/Create.php @@ -0,0 +1,422 @@ + "AI", + "SubPageID" => "AISkin" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/Skin/Classes/Skin.php'; +include dirname(__FILE__) . '/../../AI/Classes/AI.php'; + +$_GeniSysAi->checkSession(); + +$Zones = $iotJumpWay->getZones(); +$Devices = $iotJumpWay->getDevices(); + +?> + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
Create Skin Classifier Device
+
+
+
+
+
+
+
+
+
+
+
+ + + Name of device +
+
+ + + Description of device +
+
+ + + Device category +
+
+ + + Type of Skin model +
+
+ + + Device IoT Agent +
+
+ + + Name of hardware device +
+
+ + + Name of hardware manufacturer +
+
+ + + Hardware model +
+
+ + + Operating system name +
+
+ + + Operating system manufacturer +
+
+ + + Operating system version +
+
+ + + Supported Communication Protocols +
+
+ +
+
+ Device Sensors + +
+
+ +
+
+ Device Actuators + +
+
+ + + Device AI Models +
+
+ + +
+
+
+
+ + + Dataset used to train and test model +
+
+ + + Dataset link +
+
+ + + Dataset author +
+
+ + + Dataset folder to be created to hold the test data +
+
+ + + Related research paper +
+
+ + + Related research paper author +
+
+ + + Related research paper DOI +
+
+ + + Related research paper link +
+
+ + + Location of device +
+
+ + + Zone of device +
+
+ + + iotJumpWay Device coordinates +
+
+ + + IP of device +
+
+ + + MAC of device +
+
+ + + Bluetooth address of device +
+
+ + + Server port of Skin classifier device +
+
+ + + Endpoint name of NGINX reverse proxy +
+
+
+
+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + + + diff --git a/Root/var/www/html/Security/GeniSysAI/Live/index.php b/Root/var/www/html/AI/Skin/Data/.keep similarity index 100% rename from Root/var/www/html/Security/GeniSysAI/Live/index.php rename to Root/var/www/html/AI/Skin/Data/.keep diff --git a/Root/var/www/html/AI/Skin/Device.php b/Root/var/www/html/AI/Skin/Device.php new file mode 100644 index 0000000..e3b2063 --- /dev/null +++ b/Root/var/www/html/AI/Skin/Device.php @@ -0,0 +1,955 @@ + "AI", + "SubPageID" => "AISkin" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/Skin/Classes/Skin.php'; +include dirname(__FILE__) . '/../../AI/Classes/AI.php'; + +$_GeniSysAi->checkSession(); + +$Locations = $iotJumpWay->getLocations(); +$Zones = $iotJumpWay->getZones(); +$Devices = $Skin->getDevices(); + +$TId = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_NUMBER_INT); +$Device = $iotJumpWay->getDevice($TId); + +list($dev1On, $dev1Off) = $iotJumpWay->getStatusShow($Device["context"]["Data"]["status"]["value"]); + +?> + + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
Skin Classifier Device #
+
+
+
+
+
+
+
+
+
+
+
+ + "> + Name of device +
+
+ + "> + Description of device +
+
+ + + Device category +
+
+ + + Type of Skin model +
+
+ + + Device IoT Agent +
+
+ + "> + Name of hardware device +
+
+ + "> + Name of hardware manufacturer +
+
+ + "> + Hardware model +
+
+ + "> + Operating system name +
+
+ + "> + Operating system manufacturer +
+
+ + "> + Operating system version +
+
+ + + Supported Communication Protocols +
+
+ +
+
+ $value): + ?> + +
+
+ + " required> +
+
+ +
+
+ + +
+ Device Sensors + +
+
+ +
+
+ $value): + ?> + +
+
+ + " required> +
+
+ +
+
+ + +
+ Device Actuators + +
+
+ + + Device AI Models +
+
+ + +
+
+
+
+ + "> + Dataset used to train and test model +
+
+ + "> + Dataset link +
+
+ + "> + Dataset author +
+
+ + "> + Dataset folder on HIAS +
+
+ + "> + Related research paper +
+
+ + "> + Related research paper author +
+
+ + "> + Related research paper DOI +
+
+ + "> + Related research paper link +
+
+ + + Location of device +
+
+ + + Zone of device +
+
+ + , "> + iotJumpWay Device coordinates +
+
+ + _helpers->oDecrypt($Device["context"]["Data"]["ip"]["value"]) : ""; ?>"> + IP of device +
+
+ + _helpers->oDecrypt($Device["context"]["Data"]["mac"]["value"]) : ""; ?>"> + MAC of device +
+
+ + _helpers->oDecrypt($Device["context"]["Data"]["bluetooth"]["address"]) : ""; ?>"> + Bluetooth address of device +
+
+ + "> + Port of Skin stream +
+
+ + "> + Endpoint name of NGINX reverse proxy +
+
+ +

+
+
+ +

+
+
+ +

+
+
+
+
+
+
+
+

+
+
+
+
Device Schema
+
+
+
+
+
+
+
+ "; ?> "; ?> +
+
+
+

+
+ +
+
+
+
+ + + + + + + + + + + + retrieveDeviceHistory($Device["context"]["Data"]["did"]["value"], 5); + if(count($history)): + foreach($history as $key => $value): + if($value["uid"]): + $user = $_GeniSysAi->getUser($value["uid"]); + $userDetails = "User ID #" . $value["uid"] . " (" . $user["name"] . ") "; + endif; + ?> + + + + + + + + + + + +
IDActionReceiptTime
# + + + /Zones//Devices//Transaction/"># + + NA + + + + +
+
+
+
+
+

+
+ +
+
+
+
+ + + + + + + + + + + + retrieveDeviceTransactions($Device["context"]["Data"]["did"]["value"], 5); + if(count($transactions)): + foreach($transactions as $key => $value): + if($value["uid"]): + $user = $_GeniSysAi->getUser($value["uid"]); + $userDetails = "User ID #" . $value["uid"] . " (" . $user["name"] . ") "; + endif; + ?> + + + + + + + + + + + +
IDActionReceiptTime
#/Zones//Devices//Transaction/">#
+
+
+
+
+

+
+
+
+
Device iotJumpWay Statuses
+
+ +
+
+
+
+
+
+ + + + + + + + + + + retrieveDeviceStatuses($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + ?> + + + + + + + + + + +
IDStatusTime
#_id;?>Status;?>Time;?>
+
+
+
+
+

+
+
+
+
Device iotJumpWay Life
+
+ +
+
+
+
+
+
+ + + + + + + + + + + retrieveDeviceLife($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + ?> + + + + + + + + + + +
IDDetailsTime
#_id;?> + CPU: Data->CPU;?>%
+ Memory: Data->Memory;?>%
+ Diskspace: Data->Diskspace;?>%
+ Temperature: Data->Temperature;?>°C
+ Latitude: Data->Latitude;?>
+ Longitude: Data->Longitude;?>
+
Time;?>
+
+
+
+
+

+
+
+
+
Device iotJumpWay Sensors
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + retrieveDeviceSensors($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + $location = $iotJumpWay->getLocation($value->Location); + ?> + + + + + + + + + + + + +
IDTypeSensorValueMessageTime
#_id;?>Type;?>Sensor;?> + Sensor == "Facial API" || $value->Sensor == "Foscam Camera" || $value->Sensor == "USB Camera") && is_array($value->Value)): + foreach($value->Value AS $key => $val): + echo $val[0] == 0 ? "Identification: Intruder
" :"Identification: User #" . $val[0] . "
"; + echo "Distance: " . $val[1] . "
"; + echo "Message: " . $val[2] . "

"; + endforeach; + else: + echo $value->Value; + endif; + ?> + +
Message;?>Time;?>
+
+
+
+
+

+
+
+
+
Device iotJumpWay Commands
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + retrieveDeviceCommands($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + $location = $iotJumpWay->getLocation($value->Location); + ?> + + + + + + + + + + + +
IDDetailsStatusTime
#_id;?>Location: #Location;?> - Status;?>Time;?>
+
+
+
+
+
+
+
+
+
+
+
Online Offline
+
+ +
+  %    +  %    +  %    +  %    +  °C +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+

+

Last Updated:

+
+
+
+
+
+
+
+
+
+
+ +
+

+
+
+
+
+
+
+
+
+ +
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["mqtt"]["username"]); ?>

+
+
+
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["mqtt"]["password"]); ?> +

Last Updated:

+

+
+
+
+
+
+
+
+
+ +
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["amqp"]["username"]) : ""; ?>

+
+
+
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["amqp"]["password"]) : ""; ?> +

Last Updated:

+

+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/AI/Skin/index.php b/Root/var/www/html/AI/Skin/index.php new file mode 100644 index 0000000..528803f --- /dev/null +++ b/Root/var/www/html/AI/Skin/index.php @@ -0,0 +1,178 @@ + "AI", + "SubPageID" => "AISkin" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/Skin/Classes/Skin.php'; + +$_GeniSysAi->checkSession(); +$Devices = $Skin->getDevices(); + +?> + + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
Skin Cancer Models
+
+
+
+
+
+
+

The Skin Cancer are a range of computer vision models for detecting skin cancer, designed by the Peter Leukemia AI Research team in collaboration with Melanoscan. The models are designed to be used on constrained devices making them suitable for IoT networks. These models use a variety of programming languages, frameworks and hardware providing.

+
+
+
+
+
+
+

In addition to using Skin Cancer detection models created by the Peter Leukemia AI Research team, you can develop your own models and connect them up to the HIAS network.

+
+
+
+
+
+
+
Skin Cancer Devices
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + $value): + ?> + + + + + + + + + + + +
IDDETAILSSTATUSACTION
# + Name:
+ Zone: # +
+
"> + +
+
"> Edit | /Classify"> Classify
+
+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/AI/TassAI/Classes/TassAI.js b/Root/var/www/html/AI/TassAI/Classes/TassAI.js new file mode 100644 index 0000000..1dd10a1 --- /dev/null +++ b/Root/var/www/html/AI/TassAI/Classes/TassAI.js @@ -0,0 +1,56 @@ +var GeniSysAI = { + Create: function() { + $.post(window.location.href, $("#genisysai_create").serialize(), function(resp) { + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + GeniSys.ResetForm("genisysai_create"); + $('.modal-title').text('GeniSyAI Security Devices'); + $('.modal-body').html("HIAS GeniSyAI Security Device ID #" + resp.GDID + " created! Please save the API keys safely. The device's credentials are provided below. The credentials can be reset in the GeniSyAI Security Devices area.

Device ID: " + resp.DID + "
MQTT User: " + resp.MU + "
MQTT Password: " + resp.MP + "

Blockchain User: " + resp.BU + "
Blockchain Pass: " + resp.BP + "

App ID: " + resp.AppID + "
App Key: " + resp.AppKey + "

" + resp.Message); + $('#responsive-modal').modal('show'); + Logging.logMessage("Core", "Forms", "Device ID #" + resp.DID + " created!"); + break; + default: + msg = "GeniSysAI Create Failed: " + resp.Message + Logging.logMessage("Core", "GeniSysAI", msg); + break; + } + }); + }, + Update: function() { + $.post(window.location.href, $("#genisysai_update").serialize(), function(resp) { + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + var fjson = JSON.stringify(resp.Schema, null, '\t'); + window.parent.$('#schema').html(fjson); + Logging.logMessage("Core", "Forms", "Device Update OK"); + $('.modal-title').text('Device Update'); + $('.modal-body').text(resp.Message); + $('#responsive-modal').modal('show'); + break; + default: + msg = "GeniSysAI Update Failed: " + resp.Message + Logging.logMessage("Core", "GeniSysAI", msg); + break; + } + }); + } +}; +$(document).ready(function() { + + $('#genisysai_create').validator().on('submit', function(e) { + if (!e.isDefaultPrevented()) { + e.preventDefault(); + GeniSysAI.Create(); + } + }); + + $('#genisysai_update').validator().on('submit', function(e) { + if (!e.isDefaultPrevented()) { + e.preventDefault(); + GeniSysAI.Update(); + } + }); + +}); \ No newline at end of file diff --git a/Root/var/www/html/AI/TassAI/Classes/TassAI.php b/Root/var/www/html/AI/TassAI/Classes/TassAI.php new file mode 100644 index 0000000..59821f5 --- /dev/null +++ b/Root/var/www/html/AI/TassAI/Classes/TassAI.php @@ -0,0 +1,1527 @@ +_GeniSys = $_GeniSys; + $this->bcc = $this->getBlockchainConf(); + $this->web3 = $this->blockchainConnection(); + $this->contract = new Contract($this->web3->provider, $this->bcc["abi"]); + $this->icontract = new Contract($this->web3->provider, $this->bcc["iabi"]); + $this->checkBlockchainPermissions(); + endif; + $this->cb = $this->getContextBrokerConf(); + } + + public function getContextBrokerConf() + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM contextbroker + "); + $pdoQuery->execute(); + $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + return $response; + } + + private function createContextHeaders() + { + $basicAuth = $_SESSION["GeniSysAI"]["User"] . ":" . $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"]); + $basicAuth = base64_encode($basicAuth); + + return [ + "Content-Type: application/json", + 'Authorization: Basic '. $basicAuth + ]; + } + + private function contextBrokerRequest($method, $endpoint, $headers, $json) + { + $path = $this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/" . $this->cb["url"] . "/" . $endpoint; + + if($method == "GET"): + $ch = curl_init(); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_URL, $path); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "POST"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "PATCH"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + endif; + + return $body; + } + + public function getBlockchainConf() + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT blockchain.*, + contracts.contract, + contracts.abi, + icontracts.contract as icontract, + icontracts.abi as iabi + FROM blockchain blockchain + INNER JOIN contracts contracts + ON contracts.id = blockchain.dc + INNER JOIN contracts icontracts + ON icontracts.id = blockchain.ic + "); + $pdoQuery->execute(); + $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + return $response; + } + + private function blockchainConnection() + { + if(isSet($_SESSION["GeniSysAI"]["Active"])): + $web3 = new Web3($this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/Blockchain/API/", 30, $_SESSION["GeniSysAI"]["User"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"])); + return $web3; + endif; + } + + private function checkBlockchainPermissions() + { + $allowed = ""; + $errr = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->call("identifierAllowed", "User", $_SESSION["GeniSysAI"]["Identifier"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$allowed, &$errr) { + if ($err !== null) { + $allowed = "FAILED"; + $errr = $err; + return; + } + $allowed = $resp; + }); + if(!$allowed): + header('Location: /Logout'); + endif; + } + + private function unlockBlockchainAccount() + { + $response = ""; + $personal = $this->web3->personal; + $personal->unlockAccount($_SESSION["GeniSysAI"]["BC"]["BCUser"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["BC"]["BCPass"]), function ($err, $unlocked) use (&$response) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + if ($unlocked) { + $response = "OK"; + } else { + $response = "FAILED"; + } + }); + + return $response; + } + + private function lockBlockchainAccount() + { + $response = ""; + $personal = $this->web3->personal; + $personal->lockAccount($_SESSION["GeniSysAI"]["BC"]["BCUser"], function ($err, $unlocked) use (&$response) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + if ($unlocked) { + $response = "OK"; + } else { + $response = "FAILED"; + } + }); + + return $response; + } + + private function createBlockchainUser($pass) + { + $newAccount = ""; + $personal = $this->web3->personal; + $personal->newAccount($pass, function ($err, $account) use (&$newAccount) { + if ($err !== null) { + $newAccount = "FAILED!"; + return; + } + $newAccount = $account; + }); + + return $newAccount; + } + + private function getBlockchainBalance() + { + $nbalance = ""; + $this->web3->eth->getBalance($_SESSION["GeniSysAI"]["BC"]["BCUser"], function ($err, $balance) use (&$nbalance) { + if ($err !== null) { + $response = "FAILED! " . $err; + return; + } + $nbalance = $balance->toString(); + }); + + return Utils::fromWei($nbalance, 'ether')[0]; + } + + private function addAmqpUser($username, $key) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpu ( + `username`, + `pw` + ) VALUES ( + :username, + :pw + ) + "); + $query->execute([ + ':username' => $username, + ':pw' => $this->_GeniSys->_helpers->oEncrypt($key) + ]); + $amid = $this->_GeniSys->_secCon->lastInsertId(); + return $amid; + } + + private function addAmqpUserVh($uid, $vhost) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvh ( + `uid`, + `vhost` + ) VALUES ( + :uid, + :vhost + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost + ]); + } + + private function addAmqpVhPerm($uid, $vhost, $rtype, $rname, $permission) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvhr ( + `uid`, + `vhost`, + `rtype`, + `rname`, + `permission` + ) VALUES ( + :uid, + :vhost, + :rtype, + :rname, + :permission + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost, + ':rtype' => $rtype, + ':rname' => $rname, + ':permission' => $permission + ]); + } + + private function addAmqpVhTopic($uid, $vhost, $rtype, $rname, $permission, $rkey) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvhrt ( + `uid`, + `vhost`, + `rtype`, + `rname`, + `permission`, + `rkey` + ) VALUES ( + :uid, + :vhost, + :rtype, + :rname, + :permission, + :rkey + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost, + ':rtype' => $rtype, + ':rname' => $rname, + ':permission' => $permission, + ':rkey' => $rkey + ]); + } + + private function storeBlockchainTransaction($action, $hash, $device = 0, $application = 0) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + INSERT INTO transactions ( + `uid`, + `did`, + `aid`, + `action`, + `hash`, + `time` + ) VALUES ( + :uid, + :did, + :aid, + :action, + :hash, + :time + ) + "); + $pdoQuery->execute([ + ":uid" => $_SESSION["GeniSysAI"]["Uid"], + ":did" => $device, + ":aid" => $application, + ":action" => $action, + ':hash' => $this->_GeniSys->_helpers->oEncrypt($hash), + ":time" => time() + ]); + $txid = $this->_GeniSys->_secCon->lastInsertId(); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + return $txid; + } + + private function storeUserHistory($action, $hash, $location = 0, $zone = 0, $device = 0, $sensor = 0, $application = 0) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + INSERT INTO history ( + `uid`, + `tlid`, + `tzid`, + `tdid`, + `tsid`, + `taid`, + `action`, + `hash`, + `time` + ) VALUES ( + :uid, + :tlid, + :tzid, + :tdid, + :tsid, + :taid, + :action, + :hash, + :time + ) + "); + $pdoQuery->execute([ + ":uid" => $_SESSION["GeniSysAI"]["Uid"], + ":tlid" => $location, + ":tzid" => $zone, + ":tdid" => $device, + ":tsid" => $sensor, + ":taid" => $application, + ":action" => $action, + ":hash" => $hash, + ":time" => time() + ]); + $txid = $this->_GeniSys->_secCon->lastInsertId(); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + return $txid; + } + + public function checkLocation($lid) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT id + FROM mqttl + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $lid + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($location["id"]): + return True; + else: + return False; + endif; + } + + public function getLocation($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttl + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $location["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $location["pub"] . "?type=Location" . $attrs, $this->createContextHeaders(), []), true); + return $location; + } + + public function checkZone($zid) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT id + FROM mqttlz + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $zid + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($location["id"]): + return True; + else: + return False; + endif; + } + + public function getZone($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttlz + WHERE id = :id + ORDER BY id DESC + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $zone=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $zone["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $zone["pub"] . "?type=Zone" . $attrs, $this->createContextHeaders(), []), true); + return $zone; + } + + public function getDevices($limit = 0) + { + $limiter = ""; + if($limit != 0): + $limiter = "&limit=" . $limit; + endif; + + $devices = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "?type=Device&category=TassAI".$limiter, $this->createContextHeaders(), []), true); + return $devices; + } + + public function getDevice($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttld + WHERE id = :id + ORDER BY id DESC + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $device=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $device["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $device["apub"] . "?type=Device" . $attrs, $this->createContextHeaders(), []), true); + return $device; + } + + public function getThing($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM things + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $thing=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $thing["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $thing["pub"] . "?type=Thing" . $attrs, $this->createContextHeaders(), []), true); + return $thing; + } + + public function getModel($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM models + WHERE id = :id + ORDER BY id DESC + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $model=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $device["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $model["pub"] . "?type=Model" . $attrs, $this->createContextHeaders(), []), true); + return $device; + } + + public function createDevice() + { + if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Location ID is required" + ]; + endif; + + if(!$this->checkLocation(filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Zone ID is required" + ]; + endif; + + if(!$this->checkZone(filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay zone does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Category is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Coordinates entity is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device model is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system version is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IP is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "MAC is required" + ]; + endif; + + if(!count($_POST["protocols"])): + return [ + "Response"=> "Failed", + "Message" => "At least one M2M protocol is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Device stream port is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "sportf", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Device stream file is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "strdir", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Device stream directory is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "sckport", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Device socket port is required" + ]; + endif; + + $unlocked = $this->unlockBlockchainAccount(); + + if($unlocked == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Unlocking HIAS Blockhain Account Failed!" + ]; + endif; + + $mqttUser = $this->_GeniSys->_helpers->generate_uuid(); + $mqttPass = $this->_GeniSys->_helpers->password(); + $mqttHash = create_hash($mqttPass); + + $pubKey = $this->_GeniSys->_helpers->generate_uuid(); + $privKey = $this->_GeniSys->_helpers->generateKey(32); + $privKeyHash = $this->_GeniSys->_helpers->createPasswordHash($privKey); + + $amqppubKey = $this->_GeniSys->_helpers->generate_uuid(); + $amqpprvKey = $this->_GeniSys->_helpers->generateKey(32); + $amqpKeyHash = $this->_GeniSys->_helpers->createPasswordHash($amqpprvKey); + + $bcPass = $this->_GeniSys->_helpers->password(); + + $lid = filter_input(INPUT_POST, 'lid', FILTER_SANITIZE_NUMBER_INT); + $location = $this->getLocation($lid); + + $zid = filter_input(INPUT_POST, 'zid', FILTER_SANITIZE_NUMBER_INT); + $zone = $this->getZone($zid); + + $ip = filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING); + $mac = filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING); + $bluetooth = filter_input(INPUT_POST, "bluetooth", FILTER_SANITIZE_STRING); + $name = filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING); + $coords = explode(",", filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)); + + $protocols = []; + foreach($_POST["protocols"] AS $key => $value): + $protocols[] = $value; + endforeach; + + $models = []; + if(isSet($_POST["ai"])): + foreach($_POST["ai"] AS $key => $value): + $model = $this->getModel($value)["context"]["Data"]; + $mname = $model["name"]["value"]; + unset($model["id"]); + unset($model["type"]); + unset($model["mid"]); + unset($model["name"]); + unset($model["description"]); + unset($model["network"]); + unset($model["language"]); + unset($model["framework"]); + unset($model["toolkit"]); + unset($model["dateCreated"]); + unset($model["dateModified"]); + $models[$mname] = $model; + endforeach; + endif; + + $sensors = []; + if(isSet($_POST["sensors"])): + foreach($_POST["sensors"] AS $key => $value): + $sensor = $this->getThing($value)["context"]["Data"]; + unset($sensor["id"]); + unset($sensor["type"]); + unset($sensor["category"]); + unset($sensor["description"]); + unset($sensor["thing"]); + unset($sensor["properties"]["image"]); + unset($sensor["dateCreated"]); + unset($sensor["dateModified"]); + $sensors[] = $sensor; + endforeach; + endif; + + $actuators = []; + if(isSet($_POST["actuators"])): + foreach($_POST["actuators"] AS $key => $value): + $actuator = $this->getThing($value)["context"]["Data"]; + unset($actuator["id"]); + unset($actuator["type"]); + unset($actuator["category"]); + unset($actuator["description"]); + unset($actuator["thing"]); + unset($actuator["properties"]["image"]); + unset($actuator["dateCreated"]); + unset($actuator["dateModeified"]); + $actuators[] = $actuator; + endforeach; + endif; + + $newBcUser = $this->createBlockchainUser($bcPass); + + if($newBcUser == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Creating New HIAS Blockhain Account Failed!" + ]; + endif; + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttld ( + `id` + ) VALUES ( + :id + ) + "); + $query->execute([ + ':id' => 0 + ]); + $did = $this->_GeniSys->_secCon->lastInsertId(); + + $data = [ + "id" => $pubKey, + "type" => "Device", + "category" => [ + "value" => [filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)] + ], + "name" => [ + "value" => $name + ], + "description" => [ + "value" => filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING) + ], + "lid" => [ + "value" => $lid, + "entity" => $location["context"]["Data"]["id"] + ], + "zid" => [ + "value" => $zid, + "entity" => $zone["context"]["Data"]["id"] + ], + "did" => [ + "value" => $did + ], + "location" => [ + "type" => "geo:json", + "value" => [ + "type" => "Point", + "coordinates" => [floatval($coords[0]), floatval($coords[1])] + ] + ], + "agent" => [ + "url" => filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING) + ], + "device" => [ + "type" => filter_input(INPUT_POST, "type", FILTER_SANITIZE_STRING), + "name" => filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING), + "model" => filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING) + ], + "proxy" => [ + "endpoint" => filter_input(INPUT_POST, "strdir", FILTER_SANITIZE_STRING) + ], + "stream" => [ + "port" => filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING), + "file" => filter_input(INPUT_POST, "sportf", FILTER_SANITIZE_STRING) + ], + "socket" => [ + "port" => filter_input(INPUT_POST, "sckport", FILTER_SANITIZE_STRING) + ], + "os" => [ + "name" => filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING), + "version" => filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING) + ], + "protocols" => $protocols, + "status" => [ + "value" => "OFFLINE", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "keys" => [ + "public" => $pubKey, + "private" => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "blockchain" => [ + "address" => $newBcUser, + "password" => $this->_GeniSys->_helpers->oEncrypt($bcPass) + ], + "mqtt" => [ + "username" => $this->_GeniSys->_helpers->oEncrypt($mqttUser), + "password" => $this->_GeniSys->_helpers->oEncrypt($mqttPass), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "coap" => [ + "username" => "", + "password" => "" + ], + "amqp" => [ + "username" => $this->_GeniSys->_helpers->oEncrypt($amqppubKey), + "password" => $this->_GeniSys->_helpers->oEncrypt($amqpprvKey), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "batteryLevel" => [ + "value" => 0.00 + ], + "cpuUsage" => [ + "value" => 0.00 + ], + "memoryUsage" => [ + "value" => 0.00 + ], + "hddUsage" => [ + "value" => 0.00 + ], + "temperature" => [ + "value" => 0.00 + ], + "ip" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($ip), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "mac" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($mac), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "bluetooth" => [ + "address" => $bluetooth ? $this->_GeniSys->_helpers->oEncrypt($bluetooth) : "", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "ai" => $models, + "sensors" => $sensors, + "actuators" => $actuators, + "dateCreated" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateFirstUsed" => [ + "type" => "DateTime", + "value" => "" + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + + $response = json_decode($this->contextBrokerRequest("POST", $this->cb["entities_url"] . "?type=Device", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttld + SET apub = :apub + WHERE id = :id + "); + $query->execute(array( + ':apub'=> $response["Entity"]["id"], + ':id'=> $did + )); + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttu ( + `lid`, + `zid`, + `did`, + `uname`, + `pw` + ) VALUES ( + :lid, + :zid, + :did, + :uname, + :pw + ) + "); + $query->execute([ + ':lid' => $lid, + ':zid' => $zid, + ':did' => $did, + ':uname' => $mqttUser, + ':pw' => $mqttHash + ]); + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttua ( + `lid`, + `zid`, + `did`, + `username`, + `topic`, + `rw` + ) VALUES ( + :lid, + :zid, + :did, + :username, + :topic, + :rw + ) + "); + $query->execute(array( + ':lid' => $lid, + ':zid' => $zid, + ':did' => $did, + ':username' => $mqttUser, + ':topic' => $location["context"]["Data"]["id"] . "/Devices/" . $zone["context"]["Data"]["id"] . "/" . $pubKey . "/#", + ':rw' => 4 + )); + + $amid = $this->addAmqpUser($amqppubKey, $amqpKeyHash); + $this->addAmqpUserVh($amid, "iotJumpWay"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "exchange", "Core", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "exchange", "Core", "write"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Life", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Life", "write"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Statuses", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Statuses", "write"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "read", "Life"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "write", "Life"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "read", "Statuses"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "write", "Statuses"); + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO tassai ( + `pub` + ) VALUES ( + :pub + ) + "); + $query->execute([ + ':pub' => $pubKey + ]); + + $hash = ""; + $msg = ""; + $actionMsg = ""; + $balanceMessage = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("registerDevice", $pubKey, $newBcUser, $lid, $zid, $did, $name, $_SESSION["GeniSysAI"]["Uid"], time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg = " HIAS Blockchain registerDevice failed!\n" . $msg; + else: + $txid = $this->storeBlockchainTransaction("Register Device", $hash, $did); + $this->storeUserHistory("Register Device", $txid, $lid, $zid, $did); + $balance = $this->getBlockchainBalance(); + $actionMsg = " HIAS Blockchain registerDevice OK!\n"; + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; + endif; + + $this->icontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["icontract"]))->send("registerAuthorized", $newBcUser, ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg .= " HIAS Blockchain registerAuthorized failed! " . $msg; + else: + $txid = $this->storeBlockchainTransaction("iotJumpWay Register Authorized", $hash, $did); + $this->storeUserHistory("Register Authorized", $txid, $lid, $zid, $did); + $balance = $this->getBlockchainBalance(); + $actionMsg .= " HIAS Blockchain registerAuthorized OK!\n"; + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; + endif; + + return [ + "Response"=> "OK", + "Message" => "Device created!" . $actionMsg . $balanceMessage, + "LID" => filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT), + "ZID" => filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT), + "DID" => $did, + "MU" => $mqttUser, + "MP" => $mqttPass, + "BU" => $newBcUser, + "BP" => $bcPass, + "AppID" => $pubKey, + "AppKey" => $privKey + ]; + else: + return [ + "Response"=> "FAILED", + "Message" => "Device creating failed" + ]; + endif; + } + + public function updateDevice() + { + $did = filter_input(INPUT_GET, "device", FILTER_SANITIZE_NUMBER_INT); + + if(!filter_input(INPUT_POST, "identifier", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Device identifier is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Location ID is required" + ]; + endif; + + if(!$this->checkLocation(filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "Zone ID is required" + ]; + endif; + + if(!$this->checkZone(filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay zone does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Category is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Description is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Coordinates entity is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Hardware device model is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system manufacturer is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system version is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Operating system version is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IoT Agent is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "IP is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "MAC is required" + ]; + endif; + + if(!count($_POST["protocols"])): + return [ + "Response"=> "Failed", + "Message" => "At least one M2M protocol is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Device stream port is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "sportf", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Device stream file is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "strdir", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Device stream directory is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "sckport", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Device socket port is required" + ]; + endif; + + $unlocked = $this->unlockBlockchainAccount(); + + if($unlocked == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Unlocking HIAS Blockhain Account Failed!" + ]; + endif; + + $ip = filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING); + $mac = filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING); + $bluetooth = filter_input(INPUT_POST, "bluetooth", FILTER_SANITIZE_STRING); + $name = filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING); + $status = filter_input(INPUT_POST, "status", FILTER_SANITIZE_STRING); + $coords = explode(",", filter_input(INPUT_POST, "coordinates", FILTER_SANITIZE_STRING)); + + $did = filter_input(INPUT_GET, "device", FILTER_SANITIZE_NUMBER_INT); + $device = $this->getDevice($did); + + $lid = filter_input(INPUT_POST, 'lid', FILTER_SANITIZE_NUMBER_INT); + $location = $this->getLocation($lid); + + $zid = filter_input(INPUT_POST, 'zid', FILTER_SANITIZE_NUMBER_INT); + $zone = $this->getZone($zid); + + $protocols = []; + foreach($_POST["protocols"] AS $key => $value): + $protocols[] = $value; + endforeach; + + $models = []; + if(isSet($_POST["ai"])): + foreach($_POST["ai"] AS $key => $value): + $model = $this->getModel($value)["context"]["Data"]; + $mname = $model["name"]["value"]; + unset($model["id"]); + unset($model["type"]); + unset($model["mid"]); + unset($model["name"]); + unset($model["description"]); + unset($model["network"]); + unset($model["language"]); + unset($model["framework"]); + unset($model["toolkit"]); + unset($model["dateCreated"]); + unset($model["dateModified"]); + $models[$mname] = $model; + endforeach; + endif; + + $sensors = []; + if(isSet($_POST["sensors"])): + foreach($_POST["sensors"] AS $key => $value): + $sensor = $this->getThing($value)["context"]["Data"]; + unset($sensor["id"]); + unset($sensor["type"]); + unset($sensor["category"]); + unset($sensor["description"]); + unset($sensor["thing"]); + unset($sensor["properties"]["image"]); + unset($sensor["dateCreated"]); + unset($sensor["dateModified"]); + $sensors[] = $sensor; + endforeach; + endif; + + $actuators = []; + if(isSet($_POST["actuators"])): + foreach($_POST["actuators"] AS $key => $value): + $actuator = $this->getThing($value)["context"]["Data"]; + unset($actuator["id"]); + unset($actuator["type"]); + unset($actuator["category"]); + unset($actuator["description"]); + unset($actuator["thing"]); + unset($actuator["properties"]["image"]); + unset($actuator["dateCreated"]); + unset($actuator["dateModeified"]); + $actuators[] = $actuator; + endforeach; + endif; + + if($device["context"]["Data"]["lid"]["value"] != $lid): + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttu + SET lid = :lid + WHERE did = :did + "); + $query->execute([ + ':lid' => $lid, + ':did' => $did + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttua + SET lid = :lid + WHERE did = :did + "); + $query->execute([ + ':lid' => $lid, + ':did' => $did + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttua + SET topic = :topicN + WHERE did = :did + & topic = :topic + "); + $query->execute([ + ':topicN' => $device["context"]["Data"]["lid"]["entity"] . "/ " . $device["context"]["Data"]["zid"]["entity"] . "/Devices/" . $device["context"]["Data"]["id"] . "/#", + ':did' => $did, + ':topic' => $location["context"]["Data"]["id"] . "/Devices/" . $zone["context"]["Data"]["id"] . "/Devices/" . $device["context"]["Data"]["id"] . "/#" + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + endif; + + $data = [ + "type" => "Device", + "category" => [ + "value" => [filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)] + ], + "name" => [ + "value" => $name + ], + "description" => [ + "value" => filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING) + ], + "lid" => [ + "value" => $lid, + "entity" => $location["context"]["Data"]["id"] + ], + "zid" => [ + "value" => $zid, + "entity" => $zone["context"]["Data"]["id"] + ], + "did" => [ + "value" => $did + ], + "location" => [ + "type" => "geo:json", + "value" => [ + "type" => "Point", + "coordinates" => [floatval($coords[0]), floatval($coords[1])] + ] + ], + "agent" => [ + "url" => filter_input(INPUT_POST, "agent", FILTER_SANITIZE_STRING) + ], + "device" => [ + "type" => filter_input(INPUT_POST, "type", FILTER_SANITIZE_STRING), + "name" => filter_input(INPUT_POST, "deviceName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "deviceManufacturer", FILTER_SANITIZE_STRING), + "model" => filter_input(INPUT_POST, "deviceModel", FILTER_SANITIZE_STRING) + ], + "proxy" => [ + "endpoint" => filter_input(INPUT_POST, "strdir", FILTER_SANITIZE_STRING) + ], + "stream" => [ + "port" => filter_input(INPUT_POST, "sport", FILTER_SANITIZE_STRING), + "file" => filter_input(INPUT_POST, "sportf", FILTER_SANITIZE_STRING) + ], + "socket" => [ + "port" => filter_input(INPUT_POST, "sckport", FILTER_SANITIZE_STRING) + ], + "os" => [ + "name" => filter_input(INPUT_POST, "osName", FILTER_SANITIZE_STRING), + "manufacturer" => filter_input(INPUT_POST, "osManufacturer", FILTER_SANITIZE_STRING), + "version" => filter_input(INPUT_POST, "osVersion", FILTER_SANITIZE_STRING) + ], + "protocols" => $protocols, + "status" => [ + "value" => $status, + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "states" => [ + "detected", + "ok" + ], + "state" => [ + "value" => "", + "timestamp" => "" + ], + "ip" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($ip), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "mac" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt($mac), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "bluetooth" => [ + "address" => $bluetooth ? $this->_GeniSys->_helpers->oEncrypt($bluetooth) : "", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "ai" => $models, + "sensors" => $sensors, + "actuators" => $actuators, + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $device["context"]["Data"]["id"] . "/attrs?type=Device", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + + $hash = ""; + $msg = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("updateDevice", $device["context"]["Data"]["id"], "Device", $lid, $zid, $did, $name, $status, time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + $balance = ""; + $balanceMessage = ""; + $actionMsg = ""; + if($hash == "FAILED"): + $actionMsg = " HIAS Blockchain updateDevice failed! " . $msg; + else: + $txid = $this->storeBlockchainTransaction("Update Device", $hash, $did); + $this->storeUserHistory("Updated Device", $txid, $lid, $zid, $did); + $balance = $this->getBlockchainBalance(); + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; + endif; + + $device = $this->getDevice($did); + + return [ + "Response"=> "OK", + "Message" => "Device updated!" . $actionMsg . $balanceMessage, + "Schema" => $device["context"]["Data"] + ]; + else: + return [ + "Response"=> "Failed", + "Message" => "There was a problem updating this device context data!" + ]; + endif; + } + + } + + $TassAI = new TassAI($_GeniSys); + + if(filter_input(INPUT_POST, "update_genisysai", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($TassAI->updateDevice())); + endif; + if(filter_input(INPUT_POST, "create_genisysai", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($TassAI->createDevice())); + endif; + if(filter_input(INPUT_POST, "reset_mqtt", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($TassAI->resetMqtt())); + endif; + if(filter_input(INPUT_POST, "reset_key", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($TassAI->resetDvcKey())); + endif; + if(filter_input(INPUT_POST, "get_tlife", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($TassAI->getLife())); + endif; diff --git a/Root/var/www/html/AI/TassAI/Create.php b/Root/var/www/html/AI/TassAI/Create.php new file mode 100644 index 0000000..eda1b1b --- /dev/null +++ b/Root/var/www/html/AI/TassAI/Create.php @@ -0,0 +1,397 @@ + "AI", + "SubPageID" => "TassAI" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/TassAI/Classes/TassAI.php'; +include dirname(__FILE__) . '/../../AI/Classes/AI.php'; + +$_GeniSysAi->checkSession(); + +$Zones = $iotJumpWay->getZones(); +$Devices = $iotJumpWay->getDevices(); + +?> + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
Create TassAI Security Camera Device
+
+
+
+
+
+
+
+
+
+
+
+ + + Name of device +
+
+ + + Description of device +
+
+ + + Device category +
+
+ + + Type of TassAI camera device +
+
+ + + Device IoT Agent +
+
+ + + Name of hardware device +
+
+ + + Name of hardware manufacturer +
+
+ + + Hardware model +
+
+ + + Operating system name +
+
+ + + Operating system manufacturer +
+
+ + + Operating system version +
+
+ + + Supported Communication Protocols +
+
+ +
+
+ Device Sensors + +
+
+ +
+
+ Device Actuators + +
+
+ + + Device AI Models +
+
+ + +
+
+
+
+ + + Location of device +
+
+ + + Zone of device +
+
+ + + iotJumpWay Device coordinates +
+
+ + + IP of device +
+
+ + + MAC of device +
+
+ + + Bluetooth address of device +
+
+ + + Stream port of TassAI camera device +
+
+ + + Name of TassAI camera stream directory +
+
+ + + Stream file of TassAI camera device +
+
+ + + Socket port of TassAI camera device +
+
+
+
+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + + + diff --git a/Root/var/www/html/AI/TassAI/Device.php b/Root/var/www/html/AI/TassAI/Device.php new file mode 100644 index 0000000..0128280 --- /dev/null +++ b/Root/var/www/html/AI/TassAI/Device.php @@ -0,0 +1,938 @@ + "AI", + "SubPageID" => "TassAI" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/TassAI/Classes/TassAI.php'; +include dirname(__FILE__) . '/../../AI/Classes/AI.php'; + +$_GeniSysAi->checkSession(); + +$Locations = $iotJumpWay->getLocations(); +$Zones = $iotJumpWay->getZones(); +$Devices = $TassAI->getDevices(); + +$TId = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_NUMBER_INT); +$Device = $iotJumpWay->getDevice($TId); + +list($dev1On, $dev1Off) = $iotJumpWay->getStatusShow($Device["context"]["Data"]["status"]["value"]); + +?> + + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
TassAI Facial Recognition Device #
+
+
+
+
+
+
+
+
+
+
+
+ + "> + Name of device +
+
+ + "> + Description of device +
+
+ + + Device category +
+
+ + + Location of TassAI camera device +
+
+ + + Device IoT Agent +
+
+ + "> + Name of hardware device +
+
+ + "> + Name of hardware manufacturer +
+
+ + "> + Hardware model +
+
+ + "> + Operating system name +
+
+ + "> + Operating system manufacturer +
+
+ + "> + Operating system version +
+
+ + + Supported Communication Protocols +
+
+ +
+
+ $value): + ?> + +
+
+ + " required> +
+
+ +
+
+ + +
+ Device Sensors + +
+
+ +
+
+ $value): + ?> + +
+
+ + " required> +
+
+ +
+
+ + +
+ Device Actuators + +
+
+ + + Device AI Models +
+
+ +

+
+
+ +

+
+
+ +

+
+
+ + "> + "> + +
+
+
+
+ + + Location of device +
+
+ + + Zone of device +
+
+ + , "> + iotJumpWay Device coordinates +
+
+ + _helpers->oDecrypt($Device["context"]["Data"]["ip"]["value"]) : ""; ?>"> + IP of device +
+
+ + _helpers->oDecrypt($Device["context"]["Data"]["mac"]["value"]) : ""; ?>"> + MAC of device +
+
+ + _helpers->oDecrypt($Device["context"]["Data"]["bluetooth"]["address"]) : ""; ?>"> + Bluetooth address of device +
+
+ + "> + Port of TassAI stream +
+
+ + "> + File of TassAI stream (.mjpg) +
+
+ + "> + Endpoint name of NGINX reverse proxy +
+
+ + "> + Port of TassAI camera socket stream +
+
+
+
+
+
+
+

+
+
+
+
Device Schema
+
+
+
+
+
+
+
+ "; ?> "; ?> +
+
+
+

+
+ +
+
+
+
+ + + + + + + + + + + + retrieveDeviceHistory($Device["context"]["Data"]["did"]["value"], 5); + if(count($history)): + foreach($history as $key => $value): + if($value["uid"]): + $user = $_GeniSysAi->getUser($value["uid"]); + $userDetails = "User ID #" . $value["uid"] . " (" . $user["name"] . ") "; + endif; + ?> + + + + + + + + + + + +
IDActionReceiptTime
# + + + /Zones//Devices//Transaction/"># + + NA + + + + +
+
+
+
+
+

+
+ +
+
+
+
+ + + + + + + + + + + + retrieveDeviceTransactions($Device["context"]["Data"]["did"]["value"], 5); + if(count($transactions)): + foreach($transactions as $key => $value): + if($value["uid"]): + $user = $_GeniSysAi->getUser($value["uid"]); + $userDetails = "User ID #" . $value["uid"] . " (" . $user["name"] . ") "; + endif; + ?> + + + + + + + + + + + +
IDActionReceiptTime
#/Zones//Devices//Transaction/">#
+
+
+
+
+

+
+
+
+
Device iotJumpWay Statuses
+
+ +
+
+
+
+
+
+ + + + + + + + + + + retrieveDeviceStatuses($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + ?> + + + + + + + + + + +
IDStatusTime
#_id;?>Status;?>Time;?>
+
+
+
+
+

+
+
+
+
Device iotJumpWay Life
+
+ +
+
+
+
+
+
+ + + + + + + + + + + retrieveDeviceLife($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + ?> + + + + + + + + + + +
IDDetailsTime
#_id;?> + CPU: Data->CPU;?>%
+ Memory: Data->Memory;?>%
+ Diskspace: Data->Diskspace;?>%
+ Temperature: Data->Temperature;?>°C
+ Latitude: Data->Latitude;?>
+ Longitude: Data->Longitude;?>
+
Time;?>
+
+
+
+
+

+
+
+
+
Device iotJumpWay Sensors
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + retrieveDeviceSensors($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + $location = $iotJumpWay->getLocation($value->Location); + ?> + + + + + + + + + + + + +
IDTypeSensorValueMessageTime
#_id;?>Type;?>Sensor;?> + Sensor == "Facial API" || $value->Sensor == "Foscam Camera" || $value->Sensor == "USB Camera") && is_array($value->Value)): + foreach($value->Value AS $key => $val): + echo $val[0] == 0 ? "Identification: Intruder
" :"Identification: User #" . $val[0] . "
"; + echo "Distance: " . $val[1] . "
"; + echo "Message: " . $val[2] . "

"; + endforeach; + else: + echo $value->Value; + endif; + ?> + +
Message;?>Time;?>
+
+
+
+
+

+
+
+
+
Device iotJumpWay Commands
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + retrieveDeviceCommands($Device["context"]["Data"]["did"]["value"], 5); + if($Statuses["Response"] == "OK"): + foreach($Statuses["ResponseData"] as $key => $value): + $location = $iotJumpWay->getLocation($value->Location); + ?> + + + + + + + + + + + +
IDDetailsStatusTime
#_id;?>Location: #Location;?> - Status;?>Time;?>
+
+
+
+
+
+
+
+
+
+
+
Online Offline
+
+ +
+  %    +  %    +  %    +  %    +  °C +
+
+
+
+
+
"> +
+
+ /" style="width: 100%;" /> +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+

+

Last Updated:

+
+
+
+
+
+
+
+
+
+
+ +
+

+
+
+
+
+
+
+
+
+ +
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["mqtt"]["username"]); ?>

+
+
+
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["mqtt"]["password"]); ?> +

Last Updated:

+

+
+
+
+
+
+
+
+
+ +
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["amqp"]["username"]) : ""; ?>

+
+
+
+ +
+

_helpers->oDecrypt($Device["context"]["Data"]["amqp"]["password"]) : ""; ?> +

Last Updated:

+

+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/iotJumpWay/Media/Images/Sensors/.htaccess b/Root/var/www/html/AI/TassAI/Live/index.php similarity index 100% rename from Root/var/www/html/iotJumpWay/Media/Images/Sensors/.htaccess rename to Root/var/www/html/AI/TassAI/Live/index.php diff --git a/Root/var/www/html/AI/TassAI/View.php b/Root/var/www/html/AI/TassAI/View.php new file mode 100644 index 0000000..271db6f --- /dev/null +++ b/Root/var/www/html/AI/TassAI/View.php @@ -0,0 +1,123 @@ + "AI", + "SubPageID" => "TassAI" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/TassAI/Classes/TassAI.php'; + +$_GeniSysAi->checkSession(); +$Devices = $TassAI->getDevices(); + +?> + + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+ + $value): + if($value["category"]["value"] != "API" && $value["status"]["value"] == "ONLINE"): + ?> + +
+
+
+
+ /" style="width: 100%;" /> +
+
+
+
+ + +
+ +
+ + + +
+ + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/AI/TassAI/index.php b/Root/var/www/html/AI/TassAI/index.php new file mode 100644 index 0000000..6b10c72 --- /dev/null +++ b/Root/var/www/html/AI/TassAI/index.php @@ -0,0 +1,172 @@ + "AI", + "SubPageID" => "TassAI" +]; + +include dirname(__FILE__) . '/../../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../../AI/TassAI/Classes/TassAI.php'; + +$_GeniSysAi->checkSession(); +$Devices = $TassAI->getDevices(); + +?> + + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
TassAI Facial Recognition Models
+
+
+
+
+
+
+

TassAI Facial Recognition Models are a range of computer vision models for facial recognition designed to be used on constrained devices making them suitable for IoT networks. These models use a variety of programming languages, frameworks and hardware providing. You can download our TassAI models from the HIAS TassAI repository. Instructions for installation are provided in the tutorials.

+
+
+
+
+
+
+
TassAI Security Camera Devices
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + $value): + ?> + + + + + + + + + + + +
IDDETAILSSTATUSACTION
# + Name:
+ Zone: # +
+
"> + +
+
"> Edit
+
+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/AI/index.php b/Root/var/www/html/AI/index.php new file mode 100644 index 0000000..becd01b --- /dev/null +++ b/Root/var/www/html/AI/index.php @@ -0,0 +1,171 @@ + "AI", + "SubPageID" => "Models" +]; + +include dirname(__FILE__) . '/../../Classes/Core/init.php'; +include dirname(__FILE__) . '/../../Classes/Core/GeniSys.php'; +include dirname(__FILE__) . '/../iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/../AI/Classes/AI.php'; + +$_GeniSysAi->checkSession(); +$Models = $AI->getModels(); + +?> + + + + + + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
HIAS Artificial Intelligence Models
+
+
+
+
+
+
+

The AI Models are Artificial Intelligence Models created for the HIAS network, designed by the Leukemia AI Research team in our research projects (Peter Moss Acute Lymphoblastic & Lymphoblastic AI Research Project and Peter Moss COVID-19 AI Research Project).

+
+
+
+
+
+
+

In addition to using Leukemia AI Research AI Models, you can develop your own models and add them to the HIAS network for use in AI devices.

+
+
+
+
+
+
+
AI Models
+
+
+
+
+
+
+
+
+ + + + + + + + + + + $value): + ?> + + + + + + + + + + +
IDDETAILSACTION
# + Name: + "> Edit
+
+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/Blockchain/CCreate.php b/Root/var/www/html/Blockchain/CCreate.php index b21ef3c..8feb2d6 100644 --- a/Root/var/www/html/Blockchain/CCreate.php +++ b/Root/var/www/html/Blockchain/CCreate.php @@ -33,7 +33,7 @@ - + diff --git a/Root/var/www/html/Blockchain/Classes/Blockchain.js b/Root/var/www/html/Blockchain/Classes/Blockchain.js index 1c9545a..58c0a88 100644 --- a/Root/var/www/html/Blockchain/Classes/Blockchain.js +++ b/Root/var/www/html/Blockchain/Classes/Blockchain.js @@ -229,7 +229,7 @@ var Blockchain = { }); }, updateConfig: function() { - $.post(window.location.href, $("#bc_config").serialize(), function(resp) { + $.post(window.location.href, $("#blockchain_update").serialize(), function(resp) { var resp = jQuery.parseJSON(resp); switch (resp.Response) { case "OK": @@ -270,7 +270,6 @@ var Blockchain = { }, transfer: function() { $.post(window.location.href, $("#send").serialize(), function(resp) { - console.log(resp); var resp = jQuery.parseJSON(resp); switch (resp.Response) { case "OK": diff --git a/Root/var/www/html/Blockchain/Classes/Blockchain.php b/Root/var/www/html/Blockchain/Classes/Blockchain.php index 2a9975e..c7ee279 100644 --- a/Root/var/www/html/Blockchain/Classes/Blockchain.php +++ b/Root/var/www/html/Blockchain/Classes/Blockchain.php @@ -246,30 +246,38 @@ public function updateConfig() $contract = new Contract($this->web3->provider, $this->bcc["abi"]); $allowed = $this->checkBlockchainPermissions($contract); - if(!filter_input(INPUT_POST, "address", FILTER_SANITIZE_STRING)): + if(!filter_input(INPUT_POST, "dc", FILTER_SANITIZE_NUMBER_INT)): return [ "Response"=> "Failed", - "Message" => "HIAS Blockchain account password is required" + "Message" => "You must select the HIAS Smart Contract" ]; endif; - if(!filter_input(INPUT_POST, "pw", FILTER_SANITIZE_STRING)): + if(!filter_input(INPUT_POST, "ic", FILTER_SANITIZE_NUMBER_INT)): return [ "Response"=> "Failed", - "Message" => "HIAS Blockchain account address is required" + "Message" => "You must select the iotJumpWay Smart Contract" + ]; + endif; + + if(!filter_input(INPUT_POST, "pc", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "You must select the Patients Smart Contract" ]; endif; $query = $this->_GeniSys->_secCon->prepare(" UPDATE blockchain - SET bcaddress = :address, - pw = :pw + SET dc = :dc, + ic = :ic, + pc = :pc "); $query->execute([ - ':address' => $this->_GeniSys->_helpers->oEncrypt(filter_input(INPUT_POST, "address", FILTER_SANITIZE_STRING)), - ':pw' => $this->_GeniSys->_helpers->oEncrypt(filter_input(INPUT_POST, "pw", FILTER_SANITIZE_STRING)) + ':dc' => filter_input(INPUT_POST, "dc", FILTER_SANITIZE_NUMBER_INT), + ':ic' => filter_input(INPUT_POST, "ic", FILTER_SANITIZE_NUMBER_INT), + ':pc' => filter_input(INPUT_POST, "pc", FILTER_SANITIZE_NUMBER_INT) ]); - $id = $this->_GeniSys->_secCon->lastInsertId(); $this->storeUserHistory("Update Blockchain Settings"); diff --git a/Root/var/www/html/Blockchain/Contract.php b/Root/var/www/html/Blockchain/Contract.php index 6bc8e7a..4641583 100644 --- a/Root/var/www/html/Blockchain/Contract.php +++ b/Root/var/www/html/Blockchain/Contract.php @@ -34,7 +34,7 @@ - + diff --git a/Root/var/www/html/Blockchain/Contracts.php b/Root/var/www/html/Blockchain/Contracts.php index 584e049..2495a8a 100644 --- a/Root/var/www/html/Blockchain/Contracts.php +++ b/Root/var/www/html/Blockchain/Contracts.php @@ -33,7 +33,7 @@ - + @@ -83,6 +83,32 @@
HIAS Blockchain Smart Contracts
+
+
+
+
+
+
+
+
+
+
+ +

The HIAS Blockchain Smart Contracts handle the core permissions for staff,device, applications and patients connected to the HIAS network, and also handle data integrity for the data published and stored on the network. In addition to the core HIAS Smart Contracts, you can create and deploy your own custom Smart Contracts for integration with the network.

+ +
+
+
+
+
+
+
+
+
+
+
+
Smart Contracts
+
@@ -150,7 +176,7 @@ - + - diff --git a/Root/var/www/html/Blockchain/index.php b/Root/var/www/html/Blockchain/index.php index 70da9bb..d4be081 100644 --- a/Root/var/www/html/Blockchain/index.php +++ b/Root/var/www/html/Blockchain/index.php @@ -33,7 +33,7 @@ - + @@ -80,7 +80,7 @@
-
HIAS Blockchain Settings
+
HIAS Blockchain
@@ -90,24 +90,13 @@
-
-
- - " autocomplete="false"> - HIAS Blockchain account -
+
- - " autocomplete="false"> - HIAS Blockchain password -
-
- - + +

The HIAS Blockchain is a private blockchain created using Ethereum. The blockchain provides permissions management for HIAS staff, devices and applications and patients, and also handles data integrity for data that is published through the HIAS network from connected devices and applications. There are three core smart contracts on the HIAS Blockchain which provide the core functionality, but you are able to create and deploy your own smart contracts and interact with them through the HIAS UI Blockchain management area.

+
-
-
@@ -117,67 +106,94 @@
-
Device History
+
HIAS Blockchain Settings
- +
-
-
- - - - - - - - - - - - retrieveBlockchainHistory(5); - if(count($history)): - foreach($history as $key => $value): - if($value["uid"]): - $user = $_GeniSysAi->getUser($value["uid"]); - $userDetails = "User ID #" . $value["uid"] . " (" . $user["name"] . ") "; - endif; - ?> - - - - - - - - - - - -
IDActionReceiptTime
# - - - "># - - NA - - - - -
+
+
+
+
+
+
+
+
+ + + HIAS Permissions Smart Contract +
+
+ + + iotJumpWay Permissions Smart Contract +
+
+ + + Patients Permissions Smart Contract +
+
+ + +
+
+
+
+
+
+
+
diff --git a/Root/var/www/html/Dashboard.php b/Root/var/www/html/Dashboard.php index 326ed17..7388d37 100644 --- a/Root/var/www/html/Dashboard.php +++ b/Root/var/www/html/Dashboard.php @@ -1,12 +1,14 @@ "Dashboard" + "PageID" => "Home" ]; + include dirname(__FILE__) . '/../Classes/Core/init.php'; include dirname(__FILE__) . '/../Classes/Core/GeniSys.php'; -include dirname(__FILE__) . '/Security/GeniSysAI/Classes/GeniSysAI.php'; +include dirname(__FILE__) . '/iotJumpWay/Classes/iotJumpWay.php'; +include dirname(__FILE__) . '/AI/TassAI/Classes/TassAI.php'; include dirname(__FILE__) . '/Data-Analysis/COVID-19/Classes/COVID19.php'; $country = "Spain"; @@ -14,7 +16,7 @@ $stat = "Deaths"; $_GeniSysAi->checkSession(); -$TDevice = $GeniSysAI->getDevice(1); +$TDevice = $iotJumpWay->getDevice(2); $stats = $_GeniSysAi->getStats(); $covid19d = $COVID19->getCOVID19Totals(); @@ -48,47 +50,47 @@ - - + + - + - + -
-
-
+
+
+
-
+
- - - + + + -
-
+
+
- +
-
-
-
-
-
- -
-
+
+
+
+
+
+ +
+
- +
@@ -97,8 +99,8 @@
-
-
+
+
COVID-19 stat; ?> this period; ?> in country; ?>
@@ -107,19 +109,19 @@
-
-
-
+
+
+
-
-
+
+
- " style="width: 100%;" /> + /" style="width: 100%;" />
@@ -253,41 +255,41 @@
- + - - - + + - - - - + + + diff --git a/Root/var/www/html/Data-Analysis/COVID-19/Classes/COVID19.php b/Root/var/www/html/Data-Analysis/COVID-19/Classes/COVID19.php index 59b16a6..5bed77e 100644 --- a/Root/var/www/html/Data-Analysis/COVID-19/Classes/COVID19.php +++ b/Root/var/www/html/Data-Analysis/COVID-19/Classes/COVID19.php @@ -17,7 +17,6 @@ function __construct($_GeniSys) $this->bcc = $this->getBlockchainConf(); $this->web3 = $this->blockchainConnection(); $this->contract = new Contract($this->web3->provider, $this->bcc["abi"]); - $this->checkBlockchainPermissions(); endif; $this->country = filter_input(INPUT_GET, "country", FILTER_SANITIZE_STRING) ? @@ -69,39 +68,6 @@ private function checkBlockchainPermissions() endif; } - private function unlockBlockchainAccount() - { - $response = ""; - $personal = $this->web3->personal; - $personal->unlockAccount($_SESSION["GeniSysAI"]["BC"]["BCUser"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["BC"]["BCPass"]), function ($err, $unlocked) use (&$response) { - if ($err !== null) { - $response = "FAILED! " . $err; - return; - } - if ($unlocked) { - $response = "OK"; - } else { - $response = "FAILED"; - } - }); - - return $response; - } - - private function getBlockchainBalance() - { - $nbalance = ""; - $this->web3->eth->getBalance($_SESSION["GeniSysAI"]["BC"]["BCUser"], function ($err, $balance) use (&$nbalance) { - if ($err !== null) { - $response = "FAILED! " . $err; - return; - } - $nbalance = $balance->toString(); - }); - - return Utils::fromWei($nbalance, 'ether')[0]; - } - private function storeUserHistory($action) { $pdoQuery = $this->_GeniSys->_secCon->prepare(" diff --git a/Test/__init__.py b/Root/var/www/html/Data-Analysis/COVID-19/Data/.keep similarity index 100% rename from Test/__init__.py rename to Root/var/www/html/Data-Analysis/COVID-19/Data/.keep diff --git a/Root/var/www/html/Data-Analysis/COVID-19/Pulls.php b/Root/var/www/html/Data-Analysis/COVID-19/Pulls.php index c127b5b..de4c75c 100644 --- a/Root/var/www/html/Data-Analysis/COVID-19/Pulls.php +++ b/Root/var/www/html/Data-Analysis/COVID-19/Pulls.php @@ -36,7 +36,7 @@ - + diff --git a/Root/var/www/html/Data-Analysis/COVID-19/index.php b/Root/var/www/html/Data-Analysis/COVID-19/index.php index 05cf592..ddd2395 100644 --- a/Root/var/www/html/Data-Analysis/COVID-19/index.php +++ b/Root/var/www/html/Data-Analysis/COVID-19/index.php @@ -47,7 +47,7 @@ - + diff --git a/Root/var/www/html/Detection/ALL/CNN/Classes/ALL.php b/Root/var/www/html/Detection/ALL/CNN/Classes/ALL.php deleted file mode 100644 index 3a2eb18..0000000 --- a/Root/var/www/html/Detection/ALL/CNN/Classes/ALL.php +++ /dev/null @@ -1,210 +0,0 @@ -_GeniSys = $_GeniSys; - - $this->bcc = $this->getBlockchainConf(); - $this->web3 = $this->blockchainConnection(); - $this->contract = new Contract($this->web3->provider, $this->bcc["abi"]); - $this->checkBlockchainPermissions(); - - $this->dataDir = "Data/"; - $this->dataDirFull = "/fserver/var/www/html/Detection/ALL/CNN/"; - $this->dataFiles = $this->dataDir . "*.jpg"; - $this->allowedFiles = ["jpg","JPG"]; - $this->api = $this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"])."/Detection/ALL/CNN/API/Inference"; - } - - public function getBlockchainConf() - { - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT blockchain.*, - contracts.contract, - contracts.abi - FROM blockchain blockchain - INNER JOIN contracts contracts - ON contracts.id = blockchain.dc - "); - $pdoQuery->execute(); - $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); - $pdoQuery->closeCursor(); - $pdoQuery = null; - return $response; - } - - private function blockchainConnection() - { - $this->web3 = new Web3($this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/Blockchain/API/", 30, $_SESSION["GeniSysAI"]["User"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"])); - - return $this->web3; - } - - private function checkBlockchainPermissions() - { - $allowed = ""; - $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->call("identifierAllowed", "User", $_SESSION["GeniSysAI"]["Identifier"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$allowed) { - if ($err !== null) { - $allowed = "FAILED"; - return; - } - $allowed = $resp[0]; - }); - - if($allowed != "true"): - header('Location: /Logout'); - endif; - } - - private function getBlockchainBalance() - { - $nbalance = ""; - $this->web3->eth->getBalance($_SESSION["GeniSysAI"]["BC"]["BCUser"], function ($err, $balance) use (&$nbalance) { - if ($err !== null) { - $response = "FAILED! " . $err; - return; - } - $nbalance = $balance->toString(); - }); - - return Utils::fromWei($nbalance, 'ether')[0]; - } - - private function storeUserHistory($action) - { - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - INSERT INTO history ( - `uid`, - `action`, - `time` - ) VALUES ( - :uid, - :action, - :time - ) - "); - $pdoQuery->execute([ - ":uid" => $_SESSION["GeniSysAI"]["Uid"], - ":action" => $action, - ":time" => time() - ]); - $txid = $this->_GeniSys->_secCon->lastInsertId(); - $pdoQuery->closeCursor(); - $pdoQuery = null; - - return $txid; - } - - public function deleteData() - { - $images = glob($this->dataFiles); - foreach( $images as $image ): - unlink($image); - endforeach; - - return [ - "Response" => "OK", - "Message" => "Deleted Acute Lymphoblastic Leukemia Image Database for Image Processing Dataset" - ]; - - } - - public function uploadData() - { - $dataCells = ''; - if(is_array($_FILES) && !empty($_FILES['alldata'])): - foreach($_FILES['alldata']['name'] as $key => $filename): - $file_name = explode(".", $filename); - if(in_array($file_name[1], $this->allowedFiles)): - $sourcePath = $_FILES["alldata"]["tmp_name"][$key]; - $targetPath = $this->dataDir . $filename; - if(!move_uploaded_file($sourcePath, $targetPath)): - return [ - "Response" => "FAILED", - "Message" => "Upload failed " . $targetPath - ]; - endif; - else: - return [ - "Response" => "FAILED", - "Message" => "Please upload jpg files" - ]; - endif; - endforeach; - - $images = glob($this->dataFiles); - $count = 1; - foreach($images as $image): - $dataCells .= "
"; - if($count%6 == 0): - $dataCells .= "
"; - endif; - $count++; - endforeach; - - else: - return [ - "Response" => "FAILED", - "Message" => "You must upload some images (jpg)" - ]; - endif; - - return [ - "Response" => "OK", - "Message" => "Data upload OK!", - "Data" => $dataCells - ]; - - } - - public function classifyData() - { - $file = $this->dataDirFull . filter_input(INPUT_POST, "im", FILTER_SANITIZE_STRING); - $mime = mime_content_type($file); - $info = pathinfo($file); - $name = $info['basename']; - $toSend = new CURLFile($file, $mime, $name); - - $headers = [ - 'Authorization: Basic '. base64_encode($_SESSION["GeniSysAI"]["User"] . ":" . $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"])) - ]; - - $ch = curl_init($this->api); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_TIMEOUT, 30); - curl_setopt($ch, CURLOPT_POSTFIELDS, [ - 'file'=> $toSend, - ]); - - $resp = curl_exec($ch); - - return json_decode($resp, true); - - } - - } - - $ALL = new ALL($_GeniSys); - - if(filter_input(INPUT_POST, "deleteData", FILTER_SANITIZE_NUMBER_INT)): - die(json_encode($ALL->deleteData())); - endif; - - if(filter_input(INPUT_POST, "uploadAllData", FILTER_SANITIZE_NUMBER_INT)): - die(json_encode($ALL->uploadData())); - endif; - - if(filter_input(INPUT_POST, "classifyData", FILTER_SANITIZE_NUMBER_INT)): - die(json_encode($ALL->classifyData())); - endif; \ No newline at end of file diff --git a/Root/var/www/html/Detection/ALL/CNN/index.php b/Root/var/www/html/Detection/ALL/CNN/index.php deleted file mode 100644 index a3bf6d2..0000000 --- a/Root/var/www/html/Detection/ALL/CNN/index.php +++ /dev/null @@ -1,190 +0,0 @@ - "Diagnosis", - "SubPageID" => "DALL" -]; - -include dirname(__FILE__) . '/../../../../Classes/Core/init.php'; -include dirname(__FILE__) . '/../../../../Classes/Core/GeniSys.php'; -include dirname(__FILE__) . '/../../../Detection/ALL/CNN/Classes/ALL.php'; - -$_GeniSysAi->checkSession(); -$stats = $_GeniSysAi->getStats(); - -?> - - - - - - - - - <?=$_GeniSys->_confs["meta_title"]; ?> - " /> - - - - - - - - - - - - - - - - - - - -
-
-
- -
- - - - - -
-
- - - -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-
-
HIAS Acute Lymphoblastic Leukemia Detection System
-
-
- -
-
-
-
-
- -

This system uses the Peter Moss Tensorflow 2.0 AllDS2020 CNN For Raspberry Pi 4 and the Acute Lymphoblastic Leukemia Image Database for Image Processing created by Fabio Scotti, Associate Professor Dipartimento di Informatica at Università degli Studi di Milano.

-

 

- -

To use this diagnosis system you need to complete the Peter Moss Tensorflow 2.0 AllDS2020 CNN For Raspberry Pi 4 tutorial from our Github account and make sure that the classification server is running and accepting connections.

-

 

- -
DISCLAIMER
-

This project should be used for research purposes only. The purpose of the project is to show the potential of Artificial Intelligence for medical support systems such as diagnosis systems. Although the classifier is accurate, and shows good results both on paper and in real world testing, it is not meant to be an alternative to professional medical diagnosis. Developers that have contributed to this project have experience in using Artificial Intelligence for detecting certain types of cancer & COVID-19. They are not a doctors, medical or cancer/COVID-19 experts. Please use this system responsibly.

-
-
-
-
-
-
-
-
-
Acute Lymphoblastic Leukemia Image Database for Image Processing Dataset
-
- -
-
-
-
- - - -
- - dataFiles); - $count = 1; - if(count($images)): - foreach( $images as $image ): - echo "
"; - if($count%6 == 0): - echo"
"; - endif; - $count++; - endforeach; - else: - echo "

Please upload your Acute Lymphoblastic Leukemia Image Database for Image Processing data.

"; - endif; - ?> - -
-
-
-
-
-
-
-
-
-
Diagnosis Results
-
-
-
-
-
-
-
- -
-
-

-
-
- -
- -
-
-
-
-
-
- - - -
- - - - - - - - - - diff --git a/Root/var/www/html/Detection/COVID-19/CNN/Classes/COVID19.js b/Root/var/www/html/Detection/COVID-19/CNN/Classes/COVID19.js deleted file mode 100644 index 0f67d1f..0000000 --- a/Root/var/www/html/Detection/COVID-19/CNN/Classes/COVID19.js +++ /dev/null @@ -1,118 +0,0 @@ -var COVID19 = { - deleteData: function() { - $.post(window.location.href, { "deleteData": 1 }, function(resp) { - console.log(resp) - var resp = jQuery.parseJSON(resp); - switch (resp.Response) { - case "OK": - $('#dataBlock').empty(); - $('#dataBlock').html("

Please upload your SARS-COV-2 Ct-Scan Dataset data.

"); - break; - default: - break; - } - }); - }, - prepareUploadForm: function() { - - var upper = document.querySelector('#dataup'), - form = new FormData(), - xhr = new XMLHttpRequest(); - - form.append('uploadCovData', 1); - - upper.addEventListener('change', function(event) { - event.preventDefault(); - - var files = this.files; - for (var i = 0, n = files.length; i < n; i++) { - var file = files[i]; - - form.append('covdata[]', file, file.name); - - xhr.onload = function() { - if (xhr.status === 200) { - var resp = jQuery.parseJSON(xhr.response); - if (resp.Response === "OK") { - $('#dataBlock').empty(); - $('#dataBlock').html(resp.Data); - COVID19.setOpacity(); - Logging.logMessage("Core", "Forms", resp.Message); - } else { - Logging.logMessage("Core", "Forms", resp.Message); - $('.modal-title').text('Data Upload Failed'); - $('.modal-body').text(resp.Message); - $('#responsive-modal').modal('show'); - } - } - } - - xhr.open('POST', ''); - xhr.send(form); - } - }); - }, - setOpacity: function() { - - $('.classify').css("opacity", "1.0"); - $('.classify').hover(function() { - $(this).stop().animate({ opacity: 0.2 }, "fast"); - }, - function() { - $(this).stop().animate({ opacity: 1.0 }, "fast"); - }); - }, - classify: function(im) { - - $('#imageView').html(""); - $("#imName").text(im); - var classification = ''; - $("#imClass").html("Diagnosis: WAITING FOR RESPONSE"); - $("#imConf").html("Confidence: WAITING FOR RESPONSE"); - $("#imResult").html("Result: WAITING FOR RESPONSE"); - $.post(window.location.href, { "classifyData": 1, "im": im }, function(resp) { - console.log(resp) - var resp = jQuery.parseJSON(resp); - switch (resp.Response) { - case "OK": - if (im.indexOf("Non-Covid") >= 0 && resp.Diagnosis == "Negative") { - classification = "True Negative"; - } else if (im.indexOf("Non-Covid") >= 0 && resp.Diagnosis == "Positive") { - classification = "False Positive"; - } else if (im.indexOf("Non-Covid") < 0 && resp.Diagnosis == "Positive") { - classification = "True Positive"; - } else if (im.indexOf("Non-Covid") < 0 && resp.Diagnosis == "Negative") { - classification = "False Negative"; - } - $("#imClass").html("Diagnosis: " + resp.Diagnosis); - $("#imConf").html("Confidence: " + resp.Confidence); - $("#imResult").html("Result: " + classification); - break; - default: - break; - } - }); - - } -}; -$(document).ready(function() { - - $("#GeniSysAI").on("click", "#uploadData", function(e) { - e.preventDefault(); - $('#dataup').trigger('click'); - }); - - $("#GeniSysAI").on("click", "#deleteData", function(e) { - e.preventDefault(); - COVID19.deleteData(); - }); - - $("#GeniSysAI").on("click", ".classify", function(e) { - e.preventDefault(); - COVID19.classify($(this).attr("id")); - }); - - COVID19.setOpacity(); - COVID19.prepareUploadForm(); - -}); \ No newline at end of file diff --git a/Root/var/www/html/Detection/COVID-19/CNN/Classes/COVID19.php b/Root/var/www/html/Detection/COVID-19/CNN/Classes/COVID19.php deleted file mode 100644 index 2558dcb..0000000 --- a/Root/var/www/html/Detection/COVID-19/CNN/Classes/COVID19.php +++ /dev/null @@ -1,209 +0,0 @@ -_GeniSys = $_GeniSys; - - $this->bcc = $this->getBlockchainConf(); - $this->web3 = $this->blockchainConnection(); - $this->contract = new Contract($this->web3->provider, $this->bcc["abi"]); - $this->checkBlockchainPermissions(); - - $this->dataDir = "Data/"; - $this->dataDirFull = "/fserver/var/www/html/Detection/COVID-19/CNN/"; - $this->dataFiles = $this->dataDir . "*.png"; - $this->allowedFiles = ["png","PNG"]; - $this->api = $this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"])."/Detection/COVID-19/CNN/API/Inference"; - } - - public function getBlockchainConf() - { - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT blockchain.*, - contracts.contract, - contracts.abi - FROM blockchain blockchain - INNER JOIN contracts contracts - ON contracts.id = blockchain.dc - "); - $pdoQuery->execute(); - $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); - $pdoQuery->closeCursor(); - $pdoQuery = null; - return $response; - } - - private function blockchainConnection() - { - $web3 = new Web3($this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/Blockchain/API/", 30, $_SESSION["GeniSysAI"]["User"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"])); - return $web3; - } - - private function checkBlockchainPermissions() - { - $allowed = ""; - $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->call("identifierAllowed", "User", $_SESSION["GeniSysAI"]["Identifier"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$allowed) { - if ($err !== null) { - $allowed = "FAILED"; - return; - } - $allowed = $resp[0]; - }); - - if($allowed != "true"): - header('Location: /Logout'); - endif; - } - - private function getBlockchainBalance() - { - $nbalance = ""; - $this->web3->eth->getBalance($_SESSION["GeniSysAI"]["BC"]["BCUser"], function ($err, $balance) use (&$nbalance) { - if ($err !== null) { - $response = "FAILED! " . $err; - return; - } - $nbalance = $balance->toString(); - }); - - return Utils::fromWei($nbalance, 'ether')[0]; - } - - private function storeUserHistory($action) - { - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - INSERT INTO history ( - `uid`, - `action`, - `time` - ) VALUES ( - :uid, - :action, - :time - ) - "); - $pdoQuery->execute([ - ":uid" => $_SESSION["GeniSysAI"]["Uid"], - ":action" => $action, - ":time" => time() - ]); - $txid = $this->_GeniSys->_secCon->lastInsertId(); - $pdoQuery->closeCursor(); - $pdoQuery = null; - - return $txid; - } - - public function deleteData() - { - $images = glob($this->dataFiles); - foreach( $images as $image ): - unlink($image); - endforeach; - - return [ - "Response" => "OK", - "Message" => "Deleted SARS-COV-2 Ct-Scan Dataset" - ]; - - } - - public function uploadData() - { - $dataCells = ''; - if(is_array($_FILES) && !empty($_FILES['covdata'])): - foreach($_FILES['covdata']['name'] as $key => $filename): - $file_name = explode(".", $filename); - if(in_array($file_name[1], $this->allowedFiles)): - $sourcePath = $_FILES["covdata"]["tmp_name"][$key]; - $targetPath = $this->dataDir . $filename; - if(!move_uploaded_file($sourcePath, $targetPath)): - return [ - "Response" => "FAILED", - "Message" => "Upload failed " . $targetPath - ]; - endif; - else: - return [ - "Response" => "FAILED", - "Message" => "Please upload png files" - ]; - endif; - endforeach; - - $images = glob($this->dataFiles); - $count = 1; - foreach($images as $image): - $dataCells .= "
"; - if($count%6 == 0): - $dataCells .= "
"; - endif; - $count++; - endforeach; - - else: - return [ - "Response" => "FAILED", - "Message" => "You must upload some images (png)" - ]; - endif; - - return [ - "Response" => "OK", - "Message" => "Data upload OK!", - "Data" => $dataCells - ]; - - } - - public function classifyData() - { - $file = $this->dataDirFull . filter_input(INPUT_POST, "im", FILTER_SANITIZE_STRING); - $mime = mime_content_type($file); - $info = pathinfo($file); - $name = $info['basename']; - $toSend = new CURLFile($file, $mime, $name); - - $headers = [ - 'Authorization: Basic '. base64_encode($_SESSION["GeniSysAI"]["User"] . ":" . $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"])) - ]; - - $ch = curl_init($this->api); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_TIMEOUT, 30); - curl_setopt($ch, CURLOPT_POSTFIELDS, [ - 'file'=> $toSend, - ]); - - $resp = curl_exec($ch); - - return json_decode($resp, true); - - } - - } - - $COVID19 = new COVID19($_GeniSys); - - if(filter_input(INPUT_POST, "deleteData", FILTER_SANITIZE_NUMBER_INT)): - die(json_encode($COVID19->deleteData())); - endif; - - if(filter_input(INPUT_POST, "uploadCovData", FILTER_SANITIZE_NUMBER_INT)): - die(json_encode($COVID19->uploadData())); - endif; - - if(filter_input(INPUT_POST, "classifyData", FILTER_SANITIZE_NUMBER_INT)): - die(json_encode($COVID19->classifyData())); - endif; \ No newline at end of file diff --git a/Root/var/www/html/Detection/COVID-19/CNN/index.php b/Root/var/www/html/Detection/COVID-19/CNN/index.php deleted file mode 100644 index 64c6502..0000000 --- a/Root/var/www/html/Detection/COVID-19/CNN/index.php +++ /dev/null @@ -1,191 +0,0 @@ - "Diagnosis", - "SubPageID" => "DCOVID19" -]; - -include dirname(__FILE__) . '/../../../../Classes/Core/init.php'; -include dirname(__FILE__) . '/../../../../Classes/Core/GeniSys.php'; -include dirname(__FILE__) . '/../../../Detection/COVID-19/CNN/Classes/COVID19.php'; - -$_GeniSysAi->checkSession(); -$stats = $_GeniSysAi->getStats(); - -?> - - - - - - - - - <?=$_GeniSys->_confs["meta_title"]; ?> - " /> - - - - - - - - - - - - - - - - - - - -
-
-
- -
- - - - - -
-
- - - -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-
-
HIAS COVID-19 Detection System
-
-
- -
-
-
-
-
- -

This system uses the Peter Moss COVID-19 AI Research Project COVID-19 Tensorflow DenseNet Classifier and the SARS-COV-2 Ct-Scan Dataset, a large dataset of CT scans for SARS-CoV-2 (COVID-19) identification created by our collaborators, Plamenlancaster: Professor Plamen Angelov from Lancaster University/ Centre Director @ Lira, & his researcher, Eduardo Soares PhD.

-

 

- -

To use this diagnosis system you need to complete the COVID-19 Tensorflow DenseNet Classifier For Raspberry Pi 4 tutorial from our Github account and make sure that the classification server is running and accepting connections.

-

 

- -
DISCLAIMER
-

This project should be used for research purposes only. The purpose of the project is to show the potential of Artificial Intelligence for medical support systems such as diagnosis systems. Although the classifier is accurate, and shows good results both on paper and in real world testing, it is not meant to be an alternative to professional medical diagnosis. Developers that have contributed to this project have experience in using Artificial Intelligence for detecting certain types of cancer & COVID-19. They are not a doctors, medical or cancer/COVID-19 experts. Please use this system responsibly.

-
-
-
-
-
-
-
-
-
SARS-COV-2 Ct-Scan Dataset
-
- -
-
-
-
- - - -
- - dataFiles); - $count = 1; - if(count($images)): - foreach( $images as $image ): - echo "
"; - if($count%6 == 0): - echo"
"; - endif; - $count++; - endforeach; - else: - echo "

Please upload your SARS-COV-2 Ct-Scan Dataset data.

"; - endif; - ?> - -
-
-
-
-
-
-
-
-
-
Diagnosis Results
-
-
-
-
-
-
-
- -
-
-

-
-
- -
- - -
-
-
-
-
-
- - - -
- - - - - - - - - - diff --git a/Root/var/www/html/GeniSysAI/Classes/NLU.php b/Root/var/www/html/GeniSysAI/Classes/NLU.php deleted file mode 100644 index d5c17a8..0000000 --- a/Root/var/www/html/GeniSysAI/Classes/NLU.php +++ /dev/null @@ -1,769 +0,0 @@ -_GeniSys = $_GeniSys; - $this->bcc = $this->getBlockchainConf(); - $this->web3 = $this->blockchainConnection(); - $this->contract = new Contract($this->web3->provider, $this->bcc["abi"]); - $this->icontract = new Contract($this->web3->provider, $this->bcc["iabi"]); - $this->checkBlockchainPermissions(); - } - - public function getBlockchainConf() - { - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT blockchain.*, - contracts.contract, - contracts.abi, - icontracts.contract as icontract, - icontracts.abi as iabi - FROM blockchain blockchain - INNER JOIN contracts contracts - ON contracts.id = blockchain.dc - INNER JOIN contracts icontracts - ON icontracts.id = blockchain.ic - "); - $pdoQuery->execute(); - $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); - $pdoQuery->closeCursor(); - $pdoQuery = null; - return $response; - } - - private function blockchainConnection() - { - $web3 = new Web3($this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/Blockchain/API/", 30, $_SESSION["GeniSysAI"]["User"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"])); - - return $web3; - } - - private function checkBlockchainPermissions() - { - $allowed = ""; - $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->call("identifierAllowed", "User", $_SESSION["GeniSysAI"]["Identifier"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$allowed) { - if ($err !== null) { - $allowed = "FAILED"; - return; - } - $allowed = $resp[0]; - }); - - if($allowed != "true"): - header('Location: /Logout'); - endif; - } - - private function unlockBlockchainAccount() - { - $response = ""; - $personal = $this->web3->personal; - $personal->unlockAccount($_SESSION["GeniSysAI"]["BC"]["BCUser"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["BC"]["BCPass"]), function ($err, $unlocked) use (&$response) { - if ($err !== null) { - $response = "FAILED! " . $err; - return; - } - if ($unlocked) { - $response = "OK"; - } else { - $response = "FAILED"; - } - }); - - return $response; - } - - private function createBlockchainUser($pass) - { - $newAccount = ""; - $personal = $this->web3->personal; - $personal->newAccount($pass, function ($err, $account) use (&$newAccount) { - if ($err !== null) { - $newAccount = "FAILED!"; - return; - } - $newAccount = $account; - }); - - return $newAccount; - } - - private function getBlockchainBalance() - { - $nbalance = ""; - $this->web3->eth->getBalance($_SESSION["GeniSysAI"]["BC"]["BCUser"], function ($err, $balance) use (&$nbalance) { - if ($err !== null) { - $response = "FAILED! " . $err; - return; - } - $nbalance = $balance->toString(); - }); - - return Utils::fromWei($nbalance, 'ether')[0]; - } - - private function storeBlockchainTransaction($action, $hash, $device = 0, $application = 0) - { - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - INSERT INTO transactions ( - `uid`, - `did`, - `aid`, - `action`, - `hash`, - `time` - ) VALUES ( - :uid, - :did, - :aid, - :action, - :hash, - :time - ) - "); - $pdoQuery->execute([ - ":uid" => $_SESSION["GeniSysAI"]["Uid"], - ":did" => $device, - ":aid" => $application, - ":action" => $action, - ':hash' => $this->_GeniSys->_helpers->oEncrypt($hash), - ":time" => time() - ]); - $txid = $this->_GeniSys->_secCon->lastInsertId(); - $pdoQuery->closeCursor(); - $pdoQuery = null; - - return $txid; - } - - private function storeUserHistory($action, $hash, $location = 0, $zone = 0, $device = 0, $sensor = 0, $application = 0) - { - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - INSERT INTO history ( - `uid`, - `tlid`, - `tzid`, - `tdid`, - `tsid`, - `taid`, - `action`, - `hash`, - `time` - ) VALUES ( - :uid, - :tlid, - :tzid, - :tdid, - :tsid, - :taid, - :action, - :hash, - :time - ) - "); - $pdoQuery->execute([ - ":uid" => $_SESSION["GeniSysAI"]["Uid"], - ":tlid" => $location, - ":tzid" => $zone, - ":tdid" => $device, - ":tsid" => $sensor, - ":taid" => $application, - ":action" => $action, - ":hash" => $hash, - ":time" => time() - ]); - $txid = $this->_GeniSys->_secCon->lastInsertId(); - $pdoQuery->closeCursor(); - $pdoQuery = null; - - return $txid; - } - - public function getDevices() - { - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT genisysainlu.id, - genisysainlu.lid, - genisysainlu.zid, - genisysainlu.did, - genisysainlu.apidir, - location.name as loc, - zone.zn as zne, - device.name as dvc, - device.status - FROM genisysainlu genisysainlu - INNER JOIN mqttld device - ON genisysainlu.did = device.id - INNER JOIN mqttl location - ON genisysainlu.lid = location.id - INNER JOIN mqttlz zone - ON genisysainlu.zid = zone.id - ORDER BY id DESC - "); - $pdoQuery->execute(); - $response=$pdoQuery->fetchAll(PDO::FETCH_ASSOC); - $pdoQuery->closeCursor(); - $pdoQuery = null; - return $response; - } - - public function getDevice($id) - { - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT genisysainlu.id, - genisysainlu.lid, - genisysainlu.zid, - genisysainlu.did, - genisysainlu.apidir, - device.status, - device.name, - device.lt, - device.lg, - device.apub, - device.bcaddress, - device.tempr, - device.hdd, - device.mem, - device.cpu, - device.mqttu, - device.mqttp, - device.ip, - device.mac - FROM genisysainlu genisysainlu - INNER JOIN mqttld device - ON device.id = genisysainlu.did - WHERE genisysainlu.id = :id - "); - $pdoQuery->execute([ - ":id" => $id - ]); - $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); - return $response; - } - - public function createDevice() - { - if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): - return [ - "Response"=> "Failed", - "Message" => "NLU device name is required" - ]; - endif; - if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): - return [ - "Response"=> "Failed", - "Message" => "iotJumpWay location id is required" - ]; - endif; - if(!filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT)): - return [ - "Response"=> "Failed", - "Message" => "iotJumpWay zone id is required" - ]; - endif; - if(!filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING)): - return [ - "Response"=> "Failed", - "Message" => "Device IP is required" - ]; - endif; - if(!filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING)): - return [ - "Response"=> "Failed", - "Message" => "Device MAC is required" - ]; - endif; - if(!filter_input(INPUT_POST, "apidir", FILTER_SANITIZE_STRING)): - return [ - "Response"=> "Failed", - "Message" => "Nginx server proxy path" - ]; - endif; - - $mqttUser = $this->_GeniSys->_helpers->generate_uuid(); - $mqttPass = $this->_GeniSys->_helpers->password(); - $mqttHash = create_hash($mqttPass); - - $pubKey = $this->_GeniSys->_helpers->generate_uuid(); - $privKey = $this->_GeniSys->_helpers->generateKey(32); - $privKeyHash = $this->_GeniSys->_helpers->createPasswordHash($privKey); - - $bcPass = $this->_GeniSys->_helpers->password(); - - $lid = filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT); - $zid = filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT); - $ip = filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING); - $mac = filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING); - - $newBcUser = $this->createBlockchainUser($bcPass); - - if($newBcUser == "FAILED"): - return [ - "Response"=> "Failed", - "Message" => "Creating New HIAS Blockhain Account Failed!" - ]; - endif; - - $query = $this->_GeniSys->_secCon->prepare(" - INSERT INTO mqttld ( - `lid`, - `zid`, - `name`, - `mqttu`, - `mqttp`, - `bcaddress`, - `bcpw`, - `apub`, - `aprv`, - `ip`, - `mac`, - `lt`, - `lg`, - `time` - ) VALUES ( - :lid, - :zid, - :name, - :mqttu, - :mqttp, - :bcaddress, - :bcpw, - :apub, - :aprv, - :ip, - :mac, - :lt, - :lg, - :time - ) - "); - $query->execute([ - ':lid' => $lid, - ':zid' => $zid, - ':name' => filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING), - ':mqttu' =>$this->_GeniSys->_helpers->oEncrypt($mqttUser), - ':mqttp' =>$this->_GeniSys->_helpers->oEncrypt($mqttPass), - ':bcaddress' => $newBcUser, - ':bcpw' => $this->_GeniSys->_helpers->oEncrypt($bcPass), - ':apub' => $pubKey, - ':aprv' => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), - ':ip' => $this->_GeniSys->_helpers->oEncrypt(filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING)), - ':mac' => $this->_GeniSys->_helpers->oEncrypt(filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING)), - ':lt' => "", - ':lg' => "", - ':time' => time() - ]); - $did = $this->_GeniSys->_secCon->lastInsertId(); - - $query = $this->_GeniSys->_secCon->prepare(" - INSERT INTO mqttu ( - `lid`, - `zid`, - `did`, - `uname`, - `pw` - ) VALUES ( - :lid, - :zid, - :did, - :uname, - :pw - ) - "); - $query->execute([ - ':lid' => $lid, - ':zid' => $zid, - ':did' => $did, - ':uname' => $mqttUser, - ':pw' => $mqttHash - ]); - - $query = $this->_GeniSys->_secCon->prepare(" - INSERT INTO mqttua ( - `lid`, - `zid`, - `did`, - `username`, - `topic`, - `rw` - ) VALUES ( - :lid, - :zid, - :did, - :username, - :topic, - :rw - ) - "); - $query->execute(array( - ':lid' => $lid, - ':zid' => $zid, - ':did' => $did, - ':username' => $mqttUser, - ':topic' => $lid."/Devices/#", - ':rw' => 4 - )); - - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE mqttl - SET devices = devices + 1 - WHERE id = :id - "); - $query->execute(array( - ':id'=>$lid - )); - - $unlocked = $this->unlockBlockchainAccount(); - - if($unlocked == "FAILED"): - return [ - "Response"=> "Failed", - "Message" => "Unlocking HIAS Blockhain Account Failed!" - ]; - endif; - - $hash = ""; - $msg = ""; - $actionMsg = ""; - $balanceMessage = ""; - $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("registerDevice", $pubKey, $newBcUser, $lid, $zid, $did, filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING), $_SESSION["GeniSysAI"]["Uid"], time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { - if ($err !== null) { - $hash = "FAILED"; - $msg = $err; - return; - } - $hash = $resp; - }); - - if($hash == "FAILED"): - $actionMsg = " HIAS Blockchain registerDevice failed! " . $msg; - else: - $txid = $this->storeBlockchainTransaction("Register Device", $hash, $did); - $this->storeUserHistory("Register Device", $txid, $lid, $zid, $did); - $balance = $this->getBlockchainBalance(); - $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; - endif; - - $this->icontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["icontract"]))->send("registerAuthorized", $newBcUser, ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { - if ($err !== null) { - $hash = "FAILED"; - $msg = $err; - return; - } - $hash = $resp; - }); - - if($hash == "FAILED"): - $actionMsg .= " HIAS Blockchain registerAuthorized failed! " . $msg; - else: - $txid = $this->storeBlockchainTransaction("iotJumpWay Register Authorized", $hash, $did); - $this->storeUserHistory("Register Authorized", $txid, $lid, $zid, $did); - $balance = $this->getBlockchainBalance(); - if($balanceMessage == ""): - $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; - endif; - endif; - - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - INSERT INTO genisysainlu ( - `lid`, - `zid`, - `did`, - `apidir` - ) VALUES ( - :lid, - :zid, - :did, - :apidir - ) - "); - $pdoQuery->execute([ - ":lid" => $lid, - ":zid" => $zid, - ":did" => $did, - ":apidir" => $this->_GeniSys->_helpers->oEncrypt(filter_input(INPUT_POST, "apidir", FILTER_SANITIZE_STRING)) - ]); - $gid = $this->_GeniSys->_secCon->lastInsertId(); - $pdoQuery->closeCursor(); - $pdoQuery = null; - - return [ - "Response"=> "OK", - "Message" => "Device created!" . $actionMsg . $balanceMessage, - "LID" => $lid, - "ZID" => $zid, - "GDID" => $gid, - "DID" => $did, - "MU" => $mqttUser, - "MP" => $mqttPass, - "BU" => $newBcUser, - "BP" => $bcPass, - "AppID" => $pubKey, - "AppKey" => $privKey - ]; - } - - public function updateDevice() - { - if(!filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT)): - return [ - "Response"=> "Failed", - "Message" => "ID is required" - ]; - endif; - if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): - return [ - "Response"=> "Failed", - "Message" => "NLU device name is required" - ]; - endif; - if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): - return [ - "Response"=> "Failed", - "Message" => "iotJumpWay location id is required" - ]; - endif; - if(!filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT)): - return [ - "Response"=> "Failed", - "Message" => "iotJumpWay zone id is required" - ]; - endif; - if(!filter_input(INPUT_POST, "did", FILTER_SANITIZE_NUMBER_INT)): - return [ - "Response"=> "Failed", - "Message" => "iotJumpWay device id is required" - ]; - endif; - if(!filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING)): - return [ - "Response"=> "Failed", - "Message" => "Device IP is required" - ]; - endif; - if(!filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING)): - return [ - "Response"=> "Failed", - "Message" => "Device MAC is required" - ]; - endif; - if(!filter_input(INPUT_POST, "apidir", FILTER_SANITIZE_STRING)): - return [ - "Response"=> "Failed", - "Message" => "Device stream directory is required" - ]; - endif; - - $name = filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING); - $lid = filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT); - $zid = filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT); - $did = filter_input(INPUT_POST, "did", FILTER_SANITIZE_NUMBER_INT); - - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - UPDATE genisysainlu - SET lid = :lid, - zid = :zid, - did = :did, - apidir = :apidir - WHERE id = :id - "); - $pdoQuery->execute([ - ":lid" => $lid, - ":zid" => $zid, - ":did" => $did, - ":apidir" => $this->_GeniSys->_helpers->oEncrypt(filter_input(INPUT_POST, "apidir", FILTER_SANITIZE_STRING)), - ":id" => filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT) - ]); - $pdoQuery->closeCursor(); - $pdoQuery = null; - - $unlocked = $this->unlockBlockchainAccount(); - - if($unlocked == "FAILED"): - return [ - "Response"=> "Failed", - "Message" => "Unlocking HIAS Blockhain Account Failed!" - ]; - endif; - - $hash = ""; - $msg = ""; - $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("updateDevice", filter_input(INPUT_POST, "identifier", FILTER_SANITIZE_STRING), "Device", $lid, $zid, $did, $name, filter_input(INPUT_POST, "status", FILTER_SANITIZE_STRING), time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { - if ($err !== null) { - $hash = "FAILED"; - $msg = $err; - return; - } - $hash = $resp; - }); - - $balance = ""; - $balanceMessage = ""; - $actionMsg = ""; - if($hash == "FAILED"): - $actionMsg = " HIAS Blockchain updateDevice failed! " . $msg; - else: - $txid = $this->storeBlockchainTransaction("Update Device", $hash, $did); - $this->storeUserHistory("Updated Device", $txid, $lid, $zid, $did); - $balance = $this->getBlockchainBalance(); - $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; - endif; - - return [ - "Response"=> "OK", - "Message" => "Device updated!" . $actionMsg . $balanceMessage - ]; - } - - public function resetMqtt() - { - $lid = filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT); - $zid = filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT); - $did = filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT); - - $mqttPass = $this->_GeniSys->_helpers->password(); - $mqttHash = create_hash($mqttPass); - - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE mqttld - SET mqttp = :mqttp - WHERE id = :id - "); - $query->execute(array( - ':mqttp' => $this->_GeniSys->_helpers->oEncrypt($mqttPass), - ':id' => $did - )); - - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE mqttu - SET pw = :pw - WHERE did = :did - "); - $query->execute(array( - ':pw' => $mqttHash, - ':did' => $did - )); - - $this->storeUserHistory("Reset Device MQTT Password", 0, $lid, $zid, $did); - - return [ - "Response"=> "OK", - "Message" => "Device MQTT password reset!", - "P" => $mqttPass - ]; - - } - - public function resetDvcKey() - { - $lid = filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT); - $zid = filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT); - $id = filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT); - - $privKey = $this->_GeniSys->_helpers->generateKey(32); - $privKeyHash = $this->_GeniSys->_helpers->createPasswordHash($privKey); - - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE mqttld - SET aprv = :aprv - WHERE id = :id - "); - $query->execute(array( - ':aprv' => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), - ':id' => $id - )); - - $this->storeUserHistory("Reset Device Key", 0, $lid, $zid, $id); - - return [ - "Response"=> "OK", - "Message" => "Device key reset!", - "P" => $privKey - ]; - - } - - public function getLife() - { - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT id, - cpu, - mem, - hdd, - tempr, - status - FROM mqttld - WHERE id = :id - "); - $pdoQuery->execute([ - ":id" => filter_input(INPUT_POST, "device", FILTER_SANITIZE_NUMBER_INT) - ]); - $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); - $pdoQuery->closeCursor(); - $pdoQuery = null; - - if($response["id"]): - return [ - 'Response'=>'OK', - 'ResponseData'=>$response - ]; - else: - return [ - 'Response'=>'FAILED' - ]; - endif; - } - - public function getMapMarkers($device) - { - if(!$device["lt"]): - $lat = $this->_GeniSys->lt; - $lng = $this->_GeniSys->lg; - else: - $lat = $device["lt"]; - $lng = $device["lg"]; - endif; - - return [$lat, $lng]; - } - - public function getStatusShow($status) - { - if($status=="ONLINE"): - $on = " "; - $off = " hide "; - else: - $on = " hide "; - $off = " "; - endif; - - return [$on, $off]; - } - - } - - $NLU = new NLU($_GeniSys); - - if(filter_input(INPUT_POST, "update_genisysai", FILTER_SANITIZE_NUMBER_INT)): - die(json_encode($NLU->updateDevice())); - endif; - if(filter_input(INPUT_POST, "create_genisysai", FILTER_SANITIZE_NUMBER_INT)): - die(json_encode($NLU->createDevice())); - endif; - if(filter_input(INPUT_POST, "reset_mqtt", FILTER_SANITIZE_NUMBER_INT)): - die(json_encode($NLU->resetMqtt())); - endif; - if(filter_input(INPUT_POST, "reset_key", FILTER_SANITIZE_NUMBER_INT)): - die(json_encode($NLU->resetDvcKey())); - endif; - if(filter_input(INPUT_POST, "get_tlife", FILTER_SANITIZE_NUMBER_INT)): - die(json_encode($NLU->getLife())); - endif; diff --git a/Root/var/www/html/GeniSysAI/Device.php b/Root/var/www/html/GeniSysAI/Device.php deleted file mode 100644 index 99c2e5b..0000000 --- a/Root/var/www/html/GeniSysAI/Device.php +++ /dev/null @@ -1,683 +0,0 @@ - "NLU", - "SubPageID" => "GeniSysAI" -]; - -include dirname(__FILE__) . '/../../Classes/Core/init.php'; -include dirname(__FILE__) . '/../../Classes/Core/GeniSys.php'; -include dirname(__FILE__) . '/../iotJumpWay/Classes/iotJumpWay.php'; -include dirname(__FILE__) . '/../GeniSysAI/Classes/NLU.php'; - -$_GeniSysAi->checkSession(); - -$Locations = $iotJumpWay->getLocations(); -$Zones = $iotJumpWay->getZones(); -$MDevices = $iotJumpWay->getMDevices(); -$Devices = $iotJumpWay->getDevices(); - -$TId = filter_input(INPUT_GET, 'genisysai', FILTER_SANITIZE_NUMBER_INT); -$TDevice = $NLU->getDevice($TId); - -list($dev1On, $dev1Off) = $NLU->getStatusShow($TDevice["status"]); -list($lat, $lng) = $NLU->getMapMarkers($TDevice); - -?> - - - - - - - - - - <?=$_GeniSys->_confs["meta_title"]; ?> - " /> - - - - - - - - - - - - - - - - - - - -
-
-
- -
- - - - - -
-
- - - -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-
-
GeniSysAI Security NLU Device #
-
-
-
-
-
-
-
-
-
-
-
- - "> - Name of GeniSysAI NLU device -
-
- - - Location of GeniSysAI NLU device -
-
- - - Zone of GeniSysAI NLU device -
-
- - - iotJumpWay device -
-
- - "> - "> - "> - "> - -
-
-
-
- - _helpers->oDecrypt($TDevice["ip"]) : ""; ?>"> - IP of GeniSysAI NLU device -
-
- - _helpers->oDecrypt($TDevice["mac"]) : ""; ?>"> - MAC of GeniSysAI NLU device -
-
- - _helpers->oDecrypt($TDevice["apidir"]) : ""; ?>"> - Nginx server proxy path -
-
-
-
-
-
-
-

-
- -
-
-
-
- - - - - - - - - - - - retrieveDeviceHistory($TDevice["did"], 5); - if(count($history)): - foreach($history as $key => $value): - if($value["uid"]): - $user = $_GeniSysAi->getUser($value["uid"]); - $userDetails = "User ID #" . $value["uid"] . " (" . $user["name"] . ") "; - endif; - ?> - - - - - - - - - - - -
IDActionReceiptTime
# - - - /Zones//Devices//Transaction/"># - - NA - - - - -
-
-
-
-
-

-
- -
-
-
-
- - - - - - - - - - - - retrieveDeviceTransactions($TDevice["did"], 5); - if(count($transactions)): - foreach($transactions as $key => $value): - if($value["uid"]): - $user = $_GeniSysAi->getUser($value["uid"]); - $userDetails = "User ID #" . $value["uid"] . " (" . $user["name"] . ") "; - endif; - ?> - - - - - - - - - - - -
IDActionReceiptTime
#/Zones//Devices//Transaction/">#
-
-
-
-
-

-
-
-
-
Device iotJumpWay Statuses
-
- -
-
-
-
-
-
- - - - - - - - - - - retrieveDeviceStatuses($TDevice["did"], 5); - if($Statuses["Response"] == "OK"): - foreach($Statuses["ResponseData"] as $key => $value): - ?> - - - - - - - - - - -
IDStatusTime
#_id;?>Status;?>Time;?>
-
-
-
-
-

-
-
-
-
Device iotJumpWay Life
-
- -
-
-
-
-
-
- - - - - - - - - - - retrieveDeviceLife($TDevice["did"], 5); - if($Statuses["Response"] == "OK"): - foreach($Statuses["ResponseData"] as $key => $value): - ?> - - - - - - - - - - -
IDDetailsTime
#_id;?> - CPU: Data->CPU;?>%
- Memory: Data->Memory;?>%
- Diskspace: Data->Diskspace;?>%
- Temperature: Data->Temperature;?>°C
- Latitude: Data->Latitude;?>
- Longitude: Data->Longitude;?>
-
Time;?>
-
-
-
-
-

-
-
-
-
Device iotJumpWay Sensors
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - retrieveDeviceSensors($TDevice["did"], 5); - if($Statuses["Response"] == "OK"): - foreach($Statuses["ResponseData"] as $key => $value): - $location = $iotJumpWay->getLocation($value->Location); - ?> - - - - - - - - - - - - -
IDTypeSensorValueMessageTime
#_id;?>Type;?>Sensor;?> - Sensor == "Facial API" || $value->Sensor == "Foscam Camera" || $value->Sensor == "USB Camera") && is_array($value->Value)): - foreach($value->Value AS $key => $val): - echo $val[0] == 0 ? "Identification: Intruder
" :"Identification: User #" . $val[0] . "
"; - echo "Distance: " . $val[1] . "
"; - echo "Message: " . $val[2] . "

"; - endforeach; - else: - echo $value->Value; - endif; - ?> - -
Message;?>Time;?>
-
-
-
-
-

-
-
-
-
Device iotJumpWay Commands
-
- -
-
-
-
-
-
- - - - - - - - - - - - retrieveDeviceCommands($TDevice["did"], 5); - if($Statuses["Response"] == "OK"): - foreach($Statuses["ResponseData"] as $key => $value): - $location = $iotJumpWay->getLocation($value->Location); - $device = $iotJumpWay->getDevice($value->From); - $devicet = $iotJumpWay->getDevice($value->To); - $zone = $iotJumpWay->getZone($value->Zone); - if(!$device["name"]): - $type = "App"; - $application = $iotJumpWay->getApplication($value->From); - $name = $application["name"]; - else: - $type = "Device"; - $name = $device["name"]; - endif; - ?> - - - - - - - - - - - -
IDLocationInfoTime
#_id;?>Location #Location;?>:
- Zone Zone != 0 ? "#" . $value->Zone . ": " . $zone["zn"] : "NA"; ?>
- From From != 0 ? "#" . $value->From . ": " . $name : "NA"; ?>
-
- Type: Type;?>
- Value: Value;?>
- Message: Message;?> -
Time;?>
-
-
-
-
-
-
-
-
-
-
-
Online Offline
-
- -
-  %    -  %    -  %    -  °C -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
- -
-

-
-
-
-
-
-
-
-
-
-
- -
-

-
-
-
-
-
-
-
-
- -
- -
-

_helpers->oDecrypt($TDevice["mqttu"]); ?>

-
-
-
- -
-

_helpers->oDecrypt($TDevice["mqttp"]); ?> -

-
-
-
-
-
-
-
- -
- - - -
- - - - - - - - - - - - - \ No newline at end of file diff --git a/Root/var/www/html/GeniSysAI/Media/CSS/GeniSys.css b/Root/var/www/html/GeniSysAI/Media/CSS/GeniSys.css deleted file mode 100644 index d4659e3..0000000 --- a/Root/var/www/html/GeniSysAI/Media/CSS/GeniSys.css +++ /dev/null @@ -1,9 +0,0 @@ -.iotJumpWayText { - font-size: 10px !important; - color: white !important; -} - -.iotJumpWayTextTitle { - font-size: 10px !important; - color: #3b8dda !important; -} \ No newline at end of file diff --git a/Root/var/www/html/GeniSysAI/Media/JS/Logging.js b/Root/var/www/html/GeniSysAI/Media/JS/Logging.js deleted file mode 100644 index 4f2f27a..0000000 --- a/Root/var/www/html/GeniSysAI/Media/JS/Logging.js +++ /dev/null @@ -1,17 +0,0 @@ -var Logging = { - - 'logMessage': function(process, messageType, message) { - console.log(new Date($.now()) + " | " + process + " | " + messageType + " | " + message); - }, - 'Console': function() { - var console = {}; - var logger = document.getElementById("logger-console"); - console.log = function(text) { - var string = ' ' + text + '4 minutes ago'; - var element = document.createElement("div"); - var txt = document.createTextNode(string); - element.prepend(txt); - logger.prepend(element); - } - } -} \ No newline at end of file diff --git a/Root/var/www/html/GeniSysAI/Media/JS/Validation.js b/Root/var/www/html/GeniSysAI/Media/JS/Validation.js deleted file mode 100644 index 7d9e93f..0000000 --- a/Root/var/www/html/GeniSysAI/Media/JS/Validation.js +++ /dev/null @@ -1,122 +0,0 @@ -var validation = { - emailRegex: /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, - phoneRegex: /^((\+[1-9]{1,4}[ \-]*)|(\([0-9]{2,3}\)[ \-]*)|([0-9]{2,4})[ \-]*)*?[0-9]{3,4}?[ \-]*[0-9]{3,4}?$/, - usernameRegex: /^[a-zA-Z0-9]+$/, - urlRegex: /(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/, - textValidation: function(id) { - var retVal = false; - switch ($("#" + id).val()) { - case "": - $("#" + id).addClass("formError"); - retVal = false; - Logging.logMessage("Core", "Validation", "Text Field Empty"); - break; - - default: - $("#" + id).removeClass("formError"); - retVal = true; - Logging.logMessage( - "Core", - "Validation", - "Text Field Validation OK (" + $("#" + id).val() + ")" - ); - } - return retVal; - }, - selectValidation: function(id) { - var retVal = false; - switch ($("#" + id).val()) { - case "": - $("#" + id).addClass("formError"); - retVal = false; - Logging.logMessage("Core", "Validation", "Select Empty"); - break; - - case undefined: - $("#" + id).addClass("formError"); - retVal = false; - Logging.logMessage("Core", "Validation", "Select Undefined"); - break; - - default: - $("#" + id).removeClass("formError"); - retVal = true; - Logging.logMessage( - "Core", - "Validation", - "Select Validation OK (" + $("#" + id).val() + ")" - ); - } - return retVal; - }, - usernameValidation: function(id) { - var retVal = false; - switch ($("#" + id).val()) { - case "": - $("#" + id).addClass("formError"); - retVal = false; - Logging.logMessage("Core", "Validation", "Username Empty"); - break; - - default: - switch (validation.usernameRegex.test($("#" + id).val())) { - case false: - $("#" + id).addClass("formError"); - retVal = false; - Logging.logMessage( - "Core", - "Validation", - "Username Validation Failed (" + $("#" + id).val() + ")" - ); - break; - - default: - $("#" + id).removeClass("formError"); - retVal = true; - Logging.logMessage( - "Core", - "Validation", - "Username Validation OK (" + $("#" + id).val() + ")" - ); - break; - } - break; - } - - return retVal; - }, - passwordValidation: function(id) { - var retVal = false; - switch ($("#" + id).val()) { - case "": - $("#" + id).addClass("formError"); - retVal = false; - Logging.logMessage("Core", "Validation", "Password Validation Failed"); - break; - - default: - $("#" + id).removeClass("formError"); - retVal = true; - Logging.logMessage("Core", "Validation", "Password Validation OK"); - break; - } - - return retVal; - } -}; - -$("#wrapper").on("focusout", ".text-validate", function() { - validation.textValidation($(this).attr("id")); -}); - -$("#wrapper").on("focusout", ".select-validate", function() { - validation.selectValidation($(this).attr("id")); -}); - -$("#wrapper").on("focusout", ".username-validate", function() { - validation.usernameValidation($(this).attr("id")); -}); - -$(".container").on("focusout", ".password-validate", function() { - validation.passwordValidation($(this).attr("id")); -}); \ No newline at end of file diff --git a/Root/var/www/html/GeniSysAI/index.php b/Root/var/www/html/GeniSysAI/index.php deleted file mode 100644 index 9071836..0000000 --- a/Root/var/www/html/GeniSysAI/index.php +++ /dev/null @@ -1,159 +0,0 @@ - "NLU", - "SubPageID" => "Home" -]; - -include dirname(__FILE__) . '/../../Classes/Core/init.php'; -include dirname(__FILE__) . '/../../Classes/Core/GeniSys.php'; -include dirname(__FILE__) . '/../GeniSysAI/Classes/NLU.php'; - -$_GeniSysAi->checkSession(); -$TDevices = $NLU->getDevices(); - -?> - - - - - - - - - - <?=$_GeniSys->_confs["meta_title"]; ?> - " /> - - - - - - - - - - - - - - - - - - - -
-
-
- -
- - - - - -
-
- - - -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-
-
GeniSysAI NLU Devices
-
-
-
-
-
-
-
-
- - - - - - - - - - - - $value): - - ?> - - - - - - - - - - - -
IDDETAILSSTATUSACTION
# - Device: # -
- Location: # -
- Zone: # -
-
-
"> - -
-
">
-
-
-
-
-
-
-
- -
- - - -
- - - - - - - - - \ No newline at end of file diff --git a/Root/var/www/html/Hospital/Beds/Bed.php b/Root/var/www/html/Hospital/Beds/Bed.php deleted file mode 100644 index a3056d2..0000000 --- a/Root/var/www/html/Hospital/Beds/Bed.php +++ /dev/null @@ -1,449 +0,0 @@ - "HIS", - "SubPageID" => "Beds" -]; - -include dirname(__FILE__) . '/../../../Classes/Core/init.php'; -include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; -include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; -include dirname(__FILE__) . '/../../Hospital/Beds/Classes/Beds.php'; - -$_GeniSysAi->checkSession(); - -$Locations = $iotJumpWay->getLocations(0, "id ASC"); -$Zones = $iotJumpWay->getZones(0, "id ASC"); -$Devices = $iotJumpWay->getDevices(0, "id ASC"); -$Applications = $iotJumpWay->getApplications(0, "id ASC"); - -$BId = filter_input(INPUT_GET, 'bed', FILTER_SANITIZE_NUMBER_INT); -$Bed = $Beds->getBed($BId); - -list($lat, $lng) = $Beds->getMapMarkers($Bed); -list($on, $off) = $Beds->getStatusShow($Bed["status"]); - -?> - - - - - - - - - - <?=$_GeniSys->_confs["meta_title"]; ?> - " /> - - - - - - - - - - - - - - - - - - - -
-
-
- -
- - - - - -
-
- - - -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-
-
Bed #
-
-
-
-
-
-
-
-
-
-
-
-
- - - iotJumpWay Location of bed -
-
- - - - - Zone of bed -
-
- - - iotJumpWay device -
-
- - "> - "> - "> - "> - -
-
-
-
- - _helpers->oDecrypt($Bed["ip"]) : ""; ?>"> - IP of bed -
-
- - _helpers->oDecrypt($Bed["mac"]) : ""; ?>"> - MAC of bed -
-
-
-
-
-
-
-
-
-

-
-
-
-
Bed History
-
- -
-
-
-
-
-
- - - - - - - - - - - - retrieveHistory($Bed["id"], 5); - if(count($history)): - foreach($history as $key => $value): - if($value["uid"]): - $user = $_GeniSysAi->getUser($value["uid"]); - $userDetails = "User ID #" . $value["uid"] . " (" . $user["name"] . ") "; - endif; - ?> - - - - - - - - - - - -
IDActionReceiptTime
# - - - /Transaction/"># - - NA - - - - -
-
-
-
-
-

-
-
-
-
Bed Transactions
-
- -
-
-
-
-
-
- - - - - - - - - - - - retrieveTransactions($Bed["id"], 5); - if(count($transactions)): - foreach($transactions as $key => $value): - if($value["uid"]): - $user = $_GeniSysAi->getUser($value["uid"]); - $userDetails = "User ID #" . $value["uid"] . " (" . $user["name"] . ") "; - endif; - ?> - - - - - - - - - - - -
IDActionReceiptTime
#/Transaction/">#
- -
-
-
-
-
-
-
-
-
-
-
Bed Device #
-
-
Online Offline
-
-
-
-
-  %    -  %    -  %    -  °C -
-
-
-
-
-
-
-
-
-
-
-
-
- -
- -
-

-
-
-
-
-
-
-
-
-
-
- -
-

-
-
-
-
-
-
-
-
- -
- -
-

_helpers->oDecrypt($Bed["mqttu"]); ?>

-
-
-
- -
-

_helpers->oDecrypt($Bed["mqttp"]); ?> -

-
-
-
-
-
-
-
- -
- - - -
- - - - - - - - - - - - - \ No newline at end of file diff --git a/Root/var/www/html/Hospital/Beds/Classes/Beds.js b/Root/var/www/html/Hospital/Beds/Classes/Beds.js deleted file mode 100644 index 49eabbc..0000000 --- a/Root/var/www/html/Hospital/Beds/Classes/Beds.js +++ /dev/null @@ -1,157 +0,0 @@ -var Beds = { - Create: function() { - $.post(window.location.href, $("#bed_create").serialize(), function(resp) { - console.log(resp) - var resp = jQuery.parseJSON(resp); - switch (resp.Response) { - case "OK": - GeniSys.ResetForm("bed_create"); - $('.modal-title').text('HIAS Bed'); - $('.modal-body').html("HIAS Bed ID #" + resp.BID + " created! The bed's credentials are provided below. The credentials can be reset in the beds area.

User ID: " + resp.BID + "
Username: " + resp.Uname + "

MQTT User: " + resp.MU + "
MQTT Password: " + resp.MP + "

Blockchain User: " + resp.BCU + "
Blockchain Pass: " + resp.BCP + "

App ID: " + resp.AppID + "
App Key: " + resp.AppKey + "
"); - $('#responsive-modal').modal('show'); - Logging.logMessage("Core", "Forms", "Bed ID #" + resp.UID + " created!"); - break; - default: - msg = "Bed Create Failed: " + resp.Message - $('.modal-title').text('HIAS Bed'); - $('.modal-body').text(msg); - $('#responsive-modal').modal('show'); - Logging.logMessage("Core", "Beds", msg); - break; - } - }); - }, - Update: function() { - $.post(window.location.href, $("#bed_update").serialize(), function(resp) { - var resp = jQuery.parseJSON(resp); - switch (resp.Response) { - case "OK": - $('.modal-title').text('Bed Update'); - $('.modal-body').text(resp.Message); - $('#responsive-modal').modal('show'); - Logging.logMessage("Core", "Beds", "Bed Updated OK"); - break; - default: - msg = "Bed Update Failed: " + resp.Message - $('.modal-title').text('Bed Update'); - $('.modal-body').text(msg); - $('#responsive-modal').modal('show'); - Logging.logMessage("Core", "Beds", msg); - break; - } - }); - }, - ResetMqtt: function() { - $.post(window.location.href, { "reset_mqtt_bed": 1, "id": $("#id").val() }, function(resp) { - var resp = jQuery.parseJSON(resp); - switch (resp.Response) { - case "OK": - $('.modal-title').text('New Password'); - $('.modal-body').text("This bed's new MQTT password is: " + resp.P); - $('#responsive-modal').modal('show'); - Logging.logMessage("Core", "Beds", "Bed MQTT Reset OK"); - $("#mqttp").text(resp.P) - break; - default: - msg = "Bed MQTT Reset Failed: " + resp.Message - Logging.logMessage("Core", "Beds", msg); - break; - } - }); - }, - ResetAppKey: function() { - $.post(window.location.href, { "reset_bd_apriv": 1, "identifier": $("#identifier").val(), "bid": $("#id").val(), "id": $("#did").val() }, - function(resp) { - console.log(resp) - var resp = jQuery.parseJSON(resp); - switch (resp.Response) { - case "OK": - Logging.logMessage("Core", "API", "User API Key Reset OK"); - $('.modal-title').text('New API Key'); - $('.modal-body').text("This bed's new API Key is: " + resp.P); - $('#responsive-modal').modal('show'); - break; - default: - msg = "User API Key Reset Failed: " + resp.Message - Logging.logMessage("Core", "API", msg); - break; - } - }); - }, - HideInputs: function() { - - $('#ip').attr('type', 'password'); - $('#mac').attr('type', 'password'); - - Beds.mqttua = $("#bedmqttu").text(); - Beds.mqttuae = $("#bedmqttu").text().replace(/\S/gi, '*'); - Beds.mqttpa = $("#bedmqttp").text(); - Beds.mqttpae = $("#bedmqttp").text().replace(/\S/gi, '*'); - Beds.bcida = $("#bcid").text(); - Beds.bcidae = $("#bcid").text().replace(/\S/gi, '*'); - Beds.idappida = $("#idappid").text(); - Beds.idappidae = $("#idappid").text().replace(/\S/gi, '*'); - - $("#bedmqttu").text(Beds.mqttuae); - $("#bedmqttp").text(Beds.mqttpae); - $("#bcid").text(Beds.bcidae); - $("#idappid").text(Beds.idappidae); - }, -}; -$(document).ready(function() { - - $('.hider').hover(function() { - $('#' + $(this).attr("id")).attr('type', 'text'); - }, function() { - $('#' + $(this).attr("id")).attr('type', 'password'); - }); - - $('#bedmqttu').hover(function() { - $("#bedmqttu").text(Beds.mqttua); - }, function() { - $("#bedmqttu").text(Beds.mqttuae); - }); - - $('#bedmqttp').hover(function() { - $("#bedmqttp").text(Beds.mqttpa); - }, function() { - $("#bedmqttp").text(Beds.bcidae); - }); - - $('#bcid').hover(function() { - $("#bcid").text(Beds.bcida); - }, function() { - $("#bcid").text(Beds.bcidae); - }); - - $('#idappid').hover(function() { - $("#idappid").text(Beds.idappida); - }, function() { - $("#idappid").text(Beds.idappidae); - }); - - $('#bed_create').validator().on('submit', function(e) { - if (!e.isDefaultPrevented()) { - e.preventDefault(); - Beds.Create(); - } - }); - - $('#bed_update').validator().on('submit', function(e) { - if (!e.isDefaultPrevented()) { - e.preventDefault(); - Beds.Update(); - } - }); - - $("#GeniSysAI").on("click", "#reset_bed_mqtt", function(e) { - e.preventDefault(); - Beds.ResetMqtt(); - }); - - $("#GeniSysAI").on("click", "#reset_bd_apriv", function(e) { - e.preventDefault(); - Beds.ResetAppKey(); - }); - -}); \ No newline at end of file diff --git a/Root/var/www/html/Hospital/Beds/Classes/Beds.php b/Root/var/www/html/Hospital/Beds/Classes/Beds.php deleted file mode 100644 index 38ec9db..0000000 --- a/Root/var/www/html/Hospital/Beds/Classes/Beds.php +++ /dev/null @@ -1,784 +0,0 @@ -_GeniSys = $_GeniSys; - $this->bcc = $this->getBlockchainConf(); - $this->web3 = $this->blockchainConnection(); - $this->contract = new Contract($this->web3->provider, $this->bcc["abi"]); - $this->icontract = new Contract($this->web3->provider, $this->bcc["iabi"]); - $this->checkBlockchainPermissions(); - } - - public function getBlockchainConf() - { - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT blockchain.*, - contracts.contract, - contracts.abi, - icontracts.contract as icontract, - icontracts.abi as iabi - FROM blockchain blockchain - INNER JOIN contracts contracts - ON contracts.id = blockchain.dc - INNER JOIN contracts icontracts - ON icontracts.id = blockchain.ic - "); - $pdoQuery->execute(); - $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); - $pdoQuery->closeCursor(); - $pdoQuery = null; - return $response; - } - - private function blockchainConnection() - { - $web3 = new Web3($this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/Blockchain/API/", 30, $_SESSION["GeniSysAI"]["User"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"])); - return $web3; - } - - private function checkBlockchainPermissions() - { - $allowed = ""; - $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->call("identifierAllowed", "User", $_SESSION["GeniSysAI"]["Identifier"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$allowed) { - if ($err !== null) { - $allowed = "FAILED"; - return; - } - $allowed = $resp[0]; - }); - if($allowed != "true"): - header('Location: /Logout'); - endif; - } - - private function checkBlockchainPatientPermissions() - { - $allowed = ""; - $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["pcontract"]))->call("userAllowed", ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$allowed) { - if ($err !== null) { - $allowed = "FAILED"; - return; - } - $allowed = $resp[0]; - }); - if($allowed != "true"): - header('Location: /Dashboard'); - endif; - } - - private function unlockBlockchainAccount() - { - $response = ""; - $personal = $this->web3->personal; - $personal->unlockAccount($_SESSION["GeniSysAI"]["BC"]["BCUser"], $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["BC"]["BCPass"]), function ($err, $unlocked) use (&$response) { - if ($err !== null) { - $response = "FAILED! " . $err; - return; - } - if ($unlocked) { - $response = "OK"; - } else { - $response = "FAILED"; - } - }); - - return $response; - } - - private function createBlockchainUser($pass) - { - $newAccount = ""; - $personal = $this->web3->personal; - $personal->newAccount($pass, function ($err, $account) use (&$newAccount) { - if ($err !== null) { - $newAccount = "FAILED!"; - return; - } - $newAccount = $account; - }); - - return $newAccount; - } - - private function getBlockchainBalance() - { - $nbalance = ""; - $this->web3->eth->getBalance($_SESSION["GeniSysAI"]["BC"]["BCUser"], function ($err, $balance) use (&$nbalance) { - if ($err !== null) { - $response = "FAILED! " . $err; - return; - } - $nbalance = $balance->toString(); - }); - - return Utils::fromWei($nbalance, 'ether')[0]; - } - - private function storeBlockchainTransaction($action, $hash, $did, $bid) - { - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - INSERT INTO transactions ( - `uid`, - `action`, - `did`, - `bid`, - `hash`, - `time` - ) VALUES ( - :uid, - :action, - :did, - :bid, - :hash, - :time - ) - "); - $pdoQuery->execute([ - ":uid" => $_SESSION["GeniSysAI"]["Uid"], - ":action" => $action, - ":did" => $did, - ":bid" => $bid, - ':hash' => $this->_GeniSys->_helpers->oEncrypt($hash), - ":time" => time() - ]); - $txid = $this->_GeniSys->_secCon->lastInsertId(); - $pdoQuery->closeCursor(); - $pdoQuery = null; - - return $txid; - } - - private function storeUserHistory($action, $hashid, $lid, $zid, $device, $bed) - { - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - INSERT INTO history ( - `uid`, - `tlid`, - `tzid`, - `tdid`, - `tbid`, - `action`, - `hash`, - `time` - ) VALUES ( - :uid, - :tlid, - :tzid, - :tdid, - :tbid, - :action, - :hash, - :time - ) - "); - $pdoQuery->execute([ - ":uid" => $_SESSION["GeniSysAI"]["Uid"], - ":tlid" => $lid, - ":tzid" => $zid, - ":tdid" => $device, - ":tbid" => $bed, - ":action" => $action, - ':hash' => $hashid, - ":time" => time() - ]); - $txid = $this->_GeniSys->_secCon->lastInsertId(); - $pdoQuery->closeCursor(); - $pdoQuery = null; - - return $txid; - } - - public function getBeds() - { - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT beds.id, - beds.lid, - beds.did, - mqttld.status, - mqttld.mqttu, - mqttld.mqttp - FROM beds beds - INNER JOIN mqttld mqttld - ON beds.id = mqttld.bid - ORDER BY id DESC - "); - $pdoQuery->execute(); - $response=$pdoQuery->fetchAll(PDO::FETCH_ASSOC); - $pdoQuery->closeCursor(); - $pdoQuery = null; - return $response; - } - - public function getBed($id) - { - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT beds.*, - mqttld.lid, - mqttld.name, - mqttld.status, - mqttld.mqttu, - mqttld.mqttp, - mqttld.apub, - mqttld.lt, - mqttld.lg, - mqttld.cpu, - mqttld.mem, - mqttld.hdd, - mqttld.tempr, - mqttld.id AS did - FROM beds beds - INNER JOIN mqttld mqttld - ON beds.did = mqttld.id - WHERE beds.id = :id - "); - $pdoQuery->execute([ - ":id" => $id - ]); - $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); - return $response; - } - - public function createBed() - { - if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): - return [ - "Response"=> "Failed", - "Message" => "iotJumpWay location id is required" - ]; - endif; - if(!filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT)): - return [ - "Response"=> "Failed", - "Message" => "iotJumpWay location id is required" - ]; - endif; - if(!filter_input(INPUT_POST, "ip", FILTER_SANITIZE_NUMBER_INT)): - return [ - "Response"=> "Failed", - "Message" => "Bed IP is required" - ]; - endif; - if(!filter_input(INPUT_POST, "mac", FILTER_SANITIZE_NUMBER_INT)): - return [ - "Response"=> "Failed", - "Message" => "Bed MAC is required" - ]; - endif; - - $unlocked = $this->unlockBlockchainAccount(); - - if($unlocked == "FAILED"): - return [ - "Response"=> "Failed", - "Message" => "Unlocking HIAS Blockhain Account Failed!" - ]; - endif; - - $lid = filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT); - $zid = filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT); - - $mqttUser = $this->_GeniSys->_helpers->generate_uuid(); - $mqttPass = $this->_GeniSys->_helpers->password(); - $mqttHash = create_hash($mqttPass); - - $pubKey = $this->_GeniSys->_helpers->generate_uuid(); - $privKey = $this->_GeniSys->_helpers->generateKey(32); - $privKeyHash = $this->_GeniSys->_helpers->createPasswordHash($privKey); - - $bcPass = $this->_GeniSys->_helpers->password(); - $newBcUser = $this->createBlockchainUser($bcPass); - - if($newBcUser == "FAILED"): - return [ - "Response"=> "Failed", - "Message" => "Creating New HIAS Blockhain Account Failed!" - ]; - endif; - - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - INSERT INTO beds ( - `lid`, - `zid`, - `bcaddress`, - `ip`, - `mac`, - `gpstime`, - `created` - ) VALUES ( - :lid, - :zid, - :bcaddress, - :ip, - :mac, - :gpstime, - :time - ) - "); - $pdoQuery->execute([ - ":lid" => filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT), - ":zid" => $zid, - ":bcaddress" => $newBcUser, - ":ip" => $this->_GeniSys->_helpers->oEncrypt(filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING)), - ":mac" => $this->_GeniSys->_helpers->oEncrypt(filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING)), - ":gpstime" => 0, - ":time" => time() - ]); - $bid = $this->_GeniSys->_secCon->lastInsertId(); - $pdoQuery->closeCursor(); - $pdoQuery = null; - - $query = $this->_GeniSys->_secCon->prepare(" - INSERT INTO mqttld ( - `lid`, - `zid`, - `name`, - `mqttu`, - `mqttp`, - `bcaddress`, - `apub`, - `aprv`, - `time` - ) VALUES ( - :lid, - :zid, - :name, - :mqttu, - :mqttp, - :bcaddress, - :apub, - :aprv, - :time - ) - "); - $query->execute([ - ':lid' => $lid, - ':zid' => $zid, - ':name' => "Bed " . $bid, - ':mqttu' => $this->_GeniSys->_helpers->oEncrypt($mqttUser), - ':mqttp' => $this->_GeniSys->_helpers->oEncrypt($mqttPass), - ':bcaddress' => $newBcUser, - ':apub' => $pubKey, - ':aprv' => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), - ':time' => time() - ]); - $did = $this->_GeniSys->_secCon->lastInsertId(); - - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE beds - SET did = :did - WHERE id = :id - "); - $query->execute(array( - ':did'=>$did, - ':id'=>$lid - )); - - $query = $this->_GeniSys->_secCon->prepare(" - INSERT INTO mqttu ( - `lid`, - `zid`, - `did`, - `bid`, - `uname`, - `pw` - ) VALUES ( - :lid, - :zid, - :did, - :bid, - :uname, - :pw - ) - "); - $query->execute([ - ":lid" => $lid, - ":zid" => $zid, - ":did" => $did, - ":bid" => $bid, - ':uname' => $mqttUser, - ':pw' => $mqttHash - ]); - - $query = $this->_GeniSys->_secCon->prepare(" - INSERT INTO mqttua ( - `lid`, - `zid`, - `did`, - `bid`, - `username`, - `topic`, - `rw` - ) VALUES ( - :lid, - :zid, - :did, - :bid, - :username, - :topic, - :rw - ) - "); - $query->execute(array( - ":lid" => $lid, - ":zid" => $zid, - ":did" => $did, - ":bid" => $bid, - ':username' => $mqttUser, - ':topic' => $lid . "/Device/" . $zid . "/" . $did . "/#", - ':rw' => 4 - )); - - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE mqttl - SET devices = devices + 1 - WHERE id = :id - "); - $query->execute(array( - ':id'=>$lid - )); - - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE mqttld - SET bid = :bid - WHERE id = :id - "); - $query->execute(array( - ':bid'=>$bid, - ':id'=>$did - )); - - $htpasswd = new Htpasswd('/etc/nginx/security/beds'); - $htpasswd->addUser($pubKey, $privKey, Htpasswd::ENCTYPE_APR_MD5); - - $hash = ""; - $msg = ""; - $actionMsg = ""; - $balanceMessage = ""; - $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("registerDevice", $pubKey, $newBcUser, $lid, $zid, $did, "Bed " . $bid, $_SESSION["GeniSysAI"]["Uid"], time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { - if ($err !== null) { - $hash = "FAILED"; - $msg = $err; - return; - } - $hash = $resp; - }); - - if($hash == "FAILED"): - $actionMsg = " HIAS Blockchain registerDevice failed! " . $msg; - else: - $txid = $this->storeBlockchainTransaction("Register Bed Device", $hash, $did, $bid); - $this->storeUserHistory("Register Bed Device", $txid, $lid, $zid, $did, $bid); - $balance = $this->getBlockchainBalance(); - $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; - endif; - - $hash = ""; - $msg = ""; - $actionMsg = ""; - $balanceMessage = ""; - $this->icontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["icontract"]))->send("registerAuthorized", $newBcUser, ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { - if ($err !== null) { - $hash = "FAILED"; - $msg = $err; - return; - } - $hash = $resp; - }); - - if($hash == "FAILED"): - $actionMsg .= " HIAS Blockchain registerAuthorized failed! " . $msg; - else: - $txid = $this->storeBlockchainTransaction("iotJumpWay Register Authorized Bed Device", $hash, $did, $bid); - $this->storeUserHistory("iotJumpWay Register Authorized Bed Device", $txid, $lid, $zid, $did, $bid); - $balance = $this->getBlockchainBalance(); - if($balanceMessage == ""): - $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; - endif; - endif; - - return [ - "Response"=> "OK", - "Message" => "Bed & device created! " . $actionMsg . $balanceMessage, - "BID" => $bid, - "Uname" => "Bed " . $bid, - "AppID" => $pubKey, - "AppKey" => $privKey, - "BCU" => $newBcUser, - "BCP" => $bcPass, - "MU" => $mqttUser, - "MP" => $mqttPass - ]; - } - - public function updateBed() - { - if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): - return [ - "Response"=> "Failed", - "Message" => "iotJumpWay location id is required" - ]; - endif; - if(!filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT)): - return [ - "Response"=> "Failed", - "Message" => "iotJumpWay location id is required" - ]; - endif; - if(!filter_input(INPUT_POST, "did", FILTER_SANITIZE_NUMBER_INT)): - return [ - "Response"=> "Failed", - "Message" => "iotJumpWay device id is required" - ]; - endif; - if(!filter_input(INPUT_POST, "ip", FILTER_SANITIZE_NUMBER_INT)): - return [ - "Response"=> "Failed", - "Message" => "Bed IP is required" - ]; - endif; - if(!filter_input(INPUT_POST, "mac", FILTER_SANITIZE_NUMBER_INT)): - return [ - "Response"=> "Failed", - "Message" => "Bed MAC is required" - ]; - endif; - - $unlocked = $this->unlockBlockchainAccount(); - - if($unlocked == "FAILED"): - return [ - "Response"=> "Failed", - "Message" => "Unlocking HIAS Blockhain Account Failed!" - ]; - endif; - - $lid = filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT); - $zid = filter_input(INPUT_POST, "zid", FILTER_SANITIZE_NUMBER_INT); - $did = filter_input(INPUT_POST, "did", FILTER_SANITIZE_NUMBER_INT); - $id = filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT); - $ip = filter_input(INPUT_POST, "ip", FILTER_SANITIZE_STRING); - $mac = filter_input(INPUT_POST, "mac", FILTER_SANITIZE_STRING); - - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - UPDATE beds - SET lid = :lid, - zid = :zid, - did = :did, - ip = :ip, - mac = :mac - WHERE id = :id - "); - $pdoQuery->execute([ - ":lid" => $lid, - ":zid" => $zid , - ":did" => $did, - ":ip" => $this->_GeniSys->_helpers->oEncrypt($ip), - ":mac" => $this->_GeniSys->_helpers->oEncrypt($mac), - ":id" => $id - ]); - $pdoQuery->closeCursor(); - $pdoQuery = null; - - $hash = ""; - $msg = ""; - $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("updateDevice", filter_input(INPUT_POST, "identifier", FILTER_SANITIZE_STRING), "Device", $lid, $zid, $did, filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING), filter_input(INPUT_POST, "status", FILTER_SANITIZE_STRING), time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { - if ($err !== null) { - $hash = "FAILED"; - $msg = $err; - return; - } - $hash = $resp; - }); - - $balance = ""; - $balanceMessage = ""; - $actionMsg = ""; - if($hash == "FAILED"): - $actionMsg = " HIAS Blockchain updateDevice failed! " . $msg; - else: - $txid = $this->storeBlockchainTransaction("iotJumpWay Update Authorized Bed Device", $hash, $did, $id); - $this->storeUserHistory("iotJumpWay Update Authorized Bed Device", $txid, $lid, $zid, $did, $id); - $balance = $this->getBlockchainBalance(); - $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; - endif; - - return [ - "Response"=> "OK", - "Message" => "Bed updated!" . $actionMsg . $balanceMessage - ]; - } - - public function resetMqtt() - { - $mqttPass = $this->_GeniSys->_helpers->password(); - $mqttHash = create_hash($mqttPass); - - $bid = filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT); - - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE mqttld - SET mqttp = :mqttp - WHERE bid = :bid - "); - $query->execute(array( - ':mqttp' => $this->_GeniSys->_helpers->oEncrypt($mqttPass), - ':bid' => $bid - )); - - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE mqttu - SET pw = :pw - WHERE bid = :bid - "); - $query->execute(array( - ':pw' => $mqttHash, - ':bid' => $bid - )); - - $this->storeUserHistory("Reset Bed MQTT Password", 0, 0, 0, 0, $bid); - - return [ - "Response"=> "OK", - "Message" => "MQTT password reset!", - "P" => $mqttPass - ]; - - } - - public function resetAppKey() - { - $identifier = filter_input(INPUT_POST, "identifier", FILTER_SANITIZE_STRING); - $bid = filter_input(INPUT_POST, "bid", FILTER_SANITIZE_NUMBER_INT); - $id = filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT); - - $privKey = $this->_GeniSys->_helpers->generateKey(32); - $privKeyHash = $this->_GeniSys->_helpers->createPasswordHash($privKey); - - $htpasswd = new Htpasswd('/etc/nginx/security/beds'); - $htpasswd->addUser($identifier, $privKey, Htpasswd::ENCTYPE_APR_MD5); - $htpasswd->updateUser($identifier, $privKey, Htpasswd::ENCTYPE_APR_MD5); - - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE mqtta - SET aprv = :aprv - WHERE id = :id - "); - $query->execute(array( - ':aprv' => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), - ':id' => $id - )); - - $this->storeUserHistory("Reset Bed Key", 0, 0, 0, $id, $bid); - - return [ - "Response"=> "OK", - "Message" => "Application key reset!", - "P" => $privKey - ]; - - } - - public function retrieveTransactions($bed, $limit = 0, $order = "") - { - if($order): - $orderer = "ORDER BY " . $order; - else: - $orderer = "ORDER BY id DESC"; - endif; - - if($limit): - $limiter = "LIMIT " . $limit; - endif; - - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT * - FROM transactions - WHERE bid = :id - $orderer - $limiter - "); - $pdoQuery->execute([ - ":id" => $bed - ]); - $response=$pdoQuery->fetchAll(PDO::FETCH_ASSOC); - return $response; - } - - public function retrieveHistory($bed, $limit = 0, $order = "") - { - if($order): - $orderer = "ORDER BY " . $order; - else: - $orderer = "ORDER BY id DESC"; - endif; - - if($limit): - $limiter = "LIMIT " . $limit; - endif; - - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT * - FROM history - WHERE tbid = :id - $orderer - $limiter - "); - $pdoQuery->execute([ - ":id" => $bed - ]); - $response=$pdoQuery->fetchAll(PDO::FETCH_ASSOC); - return $response; - } - - public function getMapMarkers($application) - { - if(!$application["lt"]): - $lat = $this->_GeniSys->lt; - $lng = $this->_GeniSys->lg; - else: - $lat = $application["lt"]; - $lng = $application["lg"]; - endif; - - return [$lat, $lng]; - } - - public function getStatusShow($status) - { - if($status=="ONLINE"): - $on = " "; - $off = " hide "; - else: - $on = " hide "; - $off = " "; - endif; - - return [$on, $off]; - } - - } - - $Beds = new Beds($_GeniSys); - - if(filter_input(INPUT_POST, "create_bed", FILTER_SANITIZE_NUMBER_INT)): - die(json_encode($Beds->createBed())); - endif; - if(filter_input(INPUT_POST, "update_bed", FILTER_SANITIZE_NUMBER_INT)): - die(json_encode($Beds->updateBed())); - endif; - if(filter_input(INPUT_POST, "reset_mqtt_bed", FILTER_SANITIZE_NUMBER_INT)): - die(json_encode($Beds->resetMqtt())); - endif; - if(filter_input(INPUT_POST, "reset_bd_apriv", FILTER_SANITIZE_NUMBER_INT)): - die(json_encode($Beds->resetAppKey())); - endif; \ No newline at end of file diff --git a/Root/var/www/html/Hospital/Beds/Create.php b/Root/var/www/html/Hospital/Beds/Create.php deleted file mode 100644 index 2163edc..0000000 --- a/Root/var/www/html/Hospital/Beds/Create.php +++ /dev/null @@ -1,189 +0,0 @@ - "HIS", - "SubPageID" => "Beds" -]; - -include dirname(__FILE__) . '/../../../Classes/Core/init.php'; -include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; -include dirname(__FILE__) . '/../../iotJumpWay/Classes/iotJumpWay.php'; -include dirname(__FILE__) . '/../../Hospital/Beds/Classes/Beds.php'; - -$_GeniSysAi->checkSession(); - -$Locations = $iotJumpWay->getLocations(0, "id ASC"); -$Zones = $iotJumpWay->getZones(0, "id ASC"); - -?> - - - - - - - - - - <?=$_GeniSys->_confs["meta_title"]; ?> - " /> - - - - - - - - - - - - - - - - - - - -
-
-
- -
- - - - - -
-
- - - -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-
-
Create hospital bed
-
-
-
-
-
-
-
-
-
-
-
-
- - - Location of bed -
-
- - - Zone of bed -
-
- - -
-
-
-
- - - IP of bed chip -
-
- - - MAC Address of bed chip -
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
- - - -
- - - - - - - - - \ No newline at end of file diff --git a/Root/var/www/html/Hospital/Beds/index.php b/Root/var/www/html/Hospital/Beds/index.php deleted file mode 100644 index b9fa358..0000000 --- a/Root/var/www/html/Hospital/Beds/index.php +++ /dev/null @@ -1,150 +0,0 @@ - "HIS", - "SubPageID" => "Beds" -]; - -include dirname(__FILE__) . '/../../../Classes/Core/init.php'; -include dirname(__FILE__) . '/../../../Classes/Core/GeniSys.php'; -include dirname(__FILE__) . '/../../Hospital/Beds/Classes/Beds.php'; - -$_GeniSysAi->checkSession(); -$Bedsa = $Beds->getBeds(); - -?> - - - - - - - - - - <?=$_GeniSys->_confs["meta_title"]; ?> - " /> - - - - - - - - - - - - - - - - - - - -
-
-
- -
- - - - - -
-
- - - -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-
-
Hospital Beds
-
-
-
-
-
-
-
-
- - - - - - - - - - - $value): - - ?> - - - - - - - - - - -
IDDETAILSACTION
# - Location: #
- Device: # -
/"> Edit
-
-
-
-
-
-
-
- -
- - - -
- - - - - - - - \ No newline at end of file diff --git a/Root/var/www/html/Hospital/Patients/Classes/Patients.js b/Root/var/www/html/Hospital/Patients/Classes/Patients.js index 0287268..c3f8ec5 100644 --- a/Root/var/www/html/Hospital/Patients/Classes/Patients.js +++ b/Root/var/www/html/Hospital/Patients/Classes/Patients.js @@ -1,7 +1,6 @@ var Patients = { Create: function() { $.post(window.location.href, $("#patient_create").serialize(), function(resp) { - console.log(resp) var resp = jQuery.parseJSON(resp); switch (resp.Response) { case "OK": @@ -20,7 +19,6 @@ var Patients = { }, Update: function() { $.post(window.location.href, $("#patient_update").serialize(), function(resp) { - console.log(resp) var resp = jQuery.parseJSON(resp); switch (resp.Response) { case "OK": @@ -40,9 +38,8 @@ var Patients = { }); }, ResetMqtt: function() { - $.post(window.location.href, { "reset_mqtt_patient": 1, "identifier": $("#identifier").val(), "pid": $("#id").val(), "id": $("#aid").val() }, + $.post(window.location.href, { "reset_mqtt_patient": 1 }, function(resp) { - console.log(resp) var resp = jQuery.parseJSON(resp); switch (resp.Response) { case "OK": @@ -61,10 +58,33 @@ var Patients = { } }); }, + ResetAppAMQP: function() { + $.post(window.location.href, { "reset_patient_amqp": 1 }, + function(resp) { + var resp = jQuery.parseJSON(resp); + switch (resp.Response) { + case "OK": + Patients.amqppa = resp.P; + Patients.amqppe = resp.P.replace(/\S/gi, '*'); + $("#pnamqpp").text(Patients.amqppe) + Logging.logMessage("Core", "Forms", "Reset OK"); + $('.modal-title').text('Reset Patient AMQP Key'); + $('.modal-body').text("This patient's new application AMQP key is: " + resp.P); + $('#responsive-modal').modal('show'); + break; + default: + msg = "Reset failed: " + resp.Message + Logging.logMessage("Core", "Forms", msg); + $('.modal-title').text('Reset Patient AMQP Key'); + $('.modal-body').text(msg); + $('#responsive-modal').modal('show'); + break; + } + }); + }, ResetAppKey: function() { - $.post(window.location.href, { "reset_pt_apriv": 1, "identifier": $("#identifier").val(), "pid": $("#id").val(), "id": $("#aid").val() }, + $.post(window.location.href, { "reset_pt_apriv": 1 }, function(resp) { - console.log var resp = jQuery.parseJSON(resp); switch (resp.Response) { case "OK": @@ -85,6 +105,10 @@ var Patients = { $('#email').attr('type', 'password'); $('#username').attr('type', 'password'); + Patients.amqpua = $("#pnamqpu").text(); + Patients.amqpue = $("#pnamqpu").text().replace(/\S/gi, '*'); + Patients.amqppa = $("#pnamqpp").text(); + Patients.amqppe = $("#pnamqpp").text().replace(/\S/gi, '*'); Patients.mqttua = $("#pntmqttu").text(); Patients.mqttuae = $("#pntmqttu").text().replace(/\S/gi, '*'); Patients.mqttpa = $("#pntmqttp").text(); @@ -94,6 +118,8 @@ var Patients = { Patients.bcida = $("#bcid").text(); Patients.bcidae = $("#bcid").text().replace(/\S/gi, '*'); + $("#pnamqpu").text(Patients.amqpue); + $("#pnamqpp").text(Patients.amqppe); $("#pntmqttu").text(Patients.mqttuae); $("#pntmqttp").text(Patients.mqttpae); $("#idappid").text(Patients.idappidae); @@ -108,6 +134,18 @@ $(document).ready(function() { $('#' + $(this).attr("id")).attr('type', 'password'); }); + $('#pnamqpu').hover(function() { + $("#pnamqpu").text(Patients.amqpua); + }, function() { + $("#pnamqpu").text(Patients.amqpue); + }); + + $('#pnamqpp').hover(function() { + $("#pnamqpp").text(Patients.amqppa); + }, function() { + $("#pnamqpp").text(Patients.amqppe); + }); + $('#pntmqttu').hover(function() { $("#pntmqttu").text(Patients.mqttua); }, function() { @@ -156,4 +194,9 @@ $(document).ready(function() { Patients.ResetAppKey(); }); + $("#GeniSysAI").on("click", "#reset_patient_amqp", function(e) { + e.preventDefault(); + Patients.ResetAppAMQP(); + }); + }); \ No newline at end of file diff --git a/Root/var/www/html/Hospital/Patients/Classes/Patients.php b/Root/var/www/html/Hospital/Patients/Classes/Patients.php index 5f28ab9..98631d0 100644 --- a/Root/var/www/html/Hospital/Patients/Classes/Patients.php +++ b/Root/var/www/html/Hospital/Patients/Classes/Patients.php @@ -19,6 +19,77 @@ function __construct($_GeniSys) $this->pcontract = new Contract($this->web3->provider, $this->bcc["pabi"]); $this->checkBlockchainPatientPermissions(); $this->icontract = new Contract($this->web3->provider, $this->bcc["iabi"]); + $this->cb = $this->getContextBrokerConf(); + } + + public function getContextBrokerConf() + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM contextbroker + "); + $pdoQuery->execute(); + $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + return $response; + } + + private function createContextHeaders() + { + $basicAuth = $_SESSION["GeniSysAI"]["User"] . ":" . $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"]); + $basicAuth = base64_encode($basicAuth); + + return [ + "Content-Type: application/json", + 'Authorization: Basic '. $basicAuth + ]; + } + + private function contextBrokerRequest($method, $endpoint, $headers, $json) + { + $path = $this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/" . $this->cb["url"] . "/" . $endpoint; + + if($method == "GET"): + $ch = curl_init(); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_URL, $path); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "POST"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "PATCH"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + endif; + + return $body; } public function getBlockchainConf() @@ -289,60 +360,124 @@ public function retrieveHistory($patient, $limit = 0, $order = "") return $response; } - public function getPatients() + public function checkLocation($lid) { $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT patients.id, - patients.username, - patients.pic, - mqtta.lid, - mqtta.status, - mqtta.mqttu, - mqtta.mqttp, - mqtta.id AS aid - FROM patients patients - INNER JOIN mqtta mqtta - ON mqtta.id = patients.aid - ORDER BY patients.id DESC + SELECT id + FROM mqttl + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $lid + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($location["id"]): + return True; + else: + return False; + endif; + } + + public function getLocation($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttl + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $location["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $location["pub"] . "?type=Location" . $attrs, $this->createContextHeaders(), []), true); + return $location; + } + + public function getPatients($limit = 0, $order = "id DESC") + { + $limiter = ""; + if($limit != 0): + $limiter = "&limit=" . $limit; + endif; + + $patients = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "?type=Patient".$limiter, $this->createContextHeaders(), []), true); + return $patients; + } + + public function getPatientCategories() + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT category + FROM cbPatientsCats + ORDER BY category ASC "); $pdoQuery->execute(); - $response=$pdoQuery->fetchAll(PDO::FETCH_ASSOC); + $categories=$pdoQuery->fetchAll(PDO::FETCH_ASSOC); $pdoQuery->closeCursor(); $pdoQuery = null; - return $response; + return $categories; } - public function getPatient($id) + public function getPatient($id, $attrs = Null) { $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT patients.*, - mqtta.id as aid, - mqtta.lid, - mqtta.status, - mqtta.mqttu, - mqtta.mqttp, - mqtta.apub, - mqtta.lt, - mqtta.lg, - mqtta.cpu, - mqtta.mem, - mqtta.hdd, - mqtta.tempr, - mqtta.id AS aid + SELECT patients.* FROM patients patients - INNER JOIN mqtta mqtta - ON patients.id = mqtta.pid WHERE patients.id = :id "); $pdoQuery->execute([ ":id" => $id ]); - $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); - return $response; + $patient=$pdoQuery->fetch(PDO::FETCH_ASSOC); + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $patient["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $patient["pub"] . "?type=Patient" . $attrs, $this->createContextHeaders(), []), true); + return $patient; } public function createPatient() { + + if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location id is required" + ]; + endif; + + if(!$this->checkLocation(filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location does not exist" + ]; + endif; + + if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Staff name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Staff username is required" + ]; + endif; if(!filter_input(INPUT_POST, "email", FILTER_SANITIZE_EMAIL)): return [ "Response"=> "Failed", @@ -350,34 +485,31 @@ public function createPatient() ]; endif; - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT id - FROM patients - WHERE email = :email - "); - $pdoQuery->execute([ - ":email" => filter_input(INPUT_POST, "email", FILTER_SANITIZE_EMAIL) - ]); - $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + if(!filter_input(INPUT_POST, "streetAddress", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Location street address is required" + ]; + endif; - if($response["id"]): + if(!filter_input(INPUT_POST, "addressLocality", FILTER_SANITIZE_STRING)): return [ "Response"=> "Failed", - "Message" => "Patient email exists" + "Message" => "Location address locality is required" ]; endif; - if(!filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING)): + if(!filter_input(INPUT_POST, "postalCode", FILTER_SANITIZE_STRING)): return [ "Response"=> "Failed", - "Message" => "Patient username is required" + "Message" => "Location postal code is required" ]; endif; - if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): + if(!filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING)): return [ "Response"=> "Failed", - "Message" => "Patient iotJumpWay location ID is required" + "Message" => "Patient username is required" ]; endif; @@ -397,30 +529,36 @@ public function createPatient() "Message" => "Patient username exists" ]; endif; - if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): - return [ - "Response"=> "Failed", - "Message" => "Patient iotJumpWay location id is required" - ]; - endif; $uPass = $this->_GeniSys->_helpers->password(); $passhash = $this->_GeniSys->_helpers->createPasswordHash($uPass); $mqttUser = $this->_GeniSys->_helpers->generate_uuid(); - $mqttPass = $this->_GeniSys->_helpers->password(); + $mqttPass = $this->_GeniSys->_helpers->generateKey(32); $mqttHash = create_hash($mqttPass); $bcPass = $this->_GeniSys->_helpers->password(); $pubKey = $this->_GeniSys->_helpers->generate_uuid(); - $privKey = $this->_GeniSys->_helpers->generate_uuid(); + $privKey = $this->_GeniSys->_helpers->generateKey(32); $privKeyHash = $this->_GeniSys->_helpers->createPasswordHash($privKey); + $amqppubKey = $this->_GeniSys->_helpers->generate_uuid(); + $amqpprvKey = $this->_GeniSys->_helpers->generateKey(32); + $amqpKeyHash = $this->_GeniSys->_helpers->createPasswordHash($amqpprvKey); + $lid = filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT); + $location = $this->getLocation($lid); + + $name = filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING); + $username = filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING); + + $active = filter_input(INPUT_POST, "active", FILTER_SANITIZE_NUMBER_INT) ? True : False; + $admitted = filter_input(INPUT_POST, "admitted", FILTER_SANITIZE_NUMBER_INT) ? True : False; + $discharged = filter_input(INPUT_POST, "admitted", FILTER_SANITIZE_NUMBER_INT) ? False : True; $htpasswd = new Htpasswd('/etc/nginx/security/patients'); - $htpasswd->addUser(filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING), $uPass, Htpasswd::ENCTYPE_APR_MD5); + $htpasswd->addUser($username, $uPass, Htpasswd::ENCTYPE_APR_MD5); $htpasswd->addUser($pubKey, $privKey, Htpasswd::ENCTYPE_APR_MD5); $unlocked = $this->unlockBlockchainAccount(); @@ -436,220 +574,256 @@ public function createPatient() $pdoQuery = $this->_GeniSys->_secCon->prepare(" INSERT INTO patients ( - `uid`, - `lid`, - `name`, - `email`, + `pub`, `username`, `password`, `bcaddress`, - `bcpass`, - `gpstime`, - `created` + `bcpass` ) VALUES ( - :uid, - :lid, - :name, - :email, + :pub, :username, :password, :bcaddress, - :bcpass, - :gpstime, - :time + :bcpass ) "); $pdoQuery->execute([ - ":uid" => $_SESSION["GeniSysAI"]["Uid"], - ":lid" => $lid, - ":name" => filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING), - ":email" => filter_input(INPUT_POST, "email", FILTER_SANITIZE_EMAIL), - ":username" => filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING), + ":pub" => $pubKey, + ":username" => $username, ":password" => $this->_GeniSys->_helpers->oEncrypt($passhash), - ":bcaddress" => $this->_GeniSys->_helpers->oEncrypt($newBcUser), - ":bcpass" => $this->_GeniSys->_helpers->oEncrypt($bcPass), - ":gpstime" => 0, - ":time" => time() + ":bcaddress" => $newBcUser, + ":bcpass" => $this->_GeniSys->_helpers->oEncrypt($bcPass) ]); $pid = $this->_GeniSys->_secCon->lastInsertId(); - $pdoQuery->closeCursor(); - $pdoQuery = null; - - $query = $this->_GeniSys->_secCon->prepare(" - INSERT INTO mqtta ( - `pid`, - `lid`, - `name`, - `mqttu`, - `mqttp`, - `apub`, - `aprv`, - `bcaddress`, - `bcpw`, - `time` - ) VALUES ( - :pid, - :lid, - :name, - :mqttu, - :mqttp, - :apub, - :aprv, - :bcaddress, - :bcpw, - :time - ) - "); - $query->execute([ - ':pid' => $pid, - ':lid' => $lid, - ':name' => "Patient " . $pid, - ':mqttu' =>$this->_GeniSys->_helpers->oEncrypt($mqttUser), - ':mqttp' =>$this->_GeniSys->_helpers->oEncrypt($mqttPass), - ':apub' => $pubKey, - ':aprv' => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), - ':bcaddress' => $this->_GeniSys->_helpers->oEncrypt($newBcUser), - ':bcpw' => $this->_GeniSys->_helpers->oEncrypt($bcPass), - ':time' => time() - ]); - $aid = $this->_GeniSys->_secCon->lastInsertId(); - $hash = ""; - $this->pcontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["pcontract"]))->send("registerPatient", $pubKey, $newBcUser, True, False, False, $pid, $aid, time(), $_SESSION["GeniSysAI"]["Uid"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash) { - if ($err !== null) { - $hash = "FAILED"; - return; - } - $hash = $resp; - }); + $data = [ + "id" => $pubKey, + "type" => "Patient", + "category" => [ + "value" => [filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)] + ], + "name" => [ + "value" => $name + ], + "username" => [ + "value" => $username + ], + "description" => [ + "value" => filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING) + ], + "email" => [ + "value" => filter_input(INPUT_POST, "email", FILTER_SANITIZE_STRING) + ], + "nfc" => [ + "value" => filter_input(INPUT_POST, "nfc", FILTER_SANITIZE_STRING) + ], + "picture" => [ + "value" => "default.png" + ], + "lid" => [ + "value" => $lid, + "entity" => $location["context"]["Data"]["id"] + ], + "pid" => [ + "value" => $pid, + "entity" => $pubKey + ], + "zid" => [ + "value" => 0, + "entity" => "", + "timestamp" => "", + "welcomed" => "" + ], + "status" => [ + "online" => "OFFLINE", + "active" => filter_input(INPUT_POST, "active", FILTER_SANITIZE_NUMBER_INT) ? filter_input(INPUT_POST, "active", FILTER_SANITIZE_NUMBER_INT) : 0, + "admitted" => filter_input(INPUT_POST, "admitted", FILTER_SANITIZE_NUMBER_INT) ? filter_input(INPUT_POST, "admitted", FILTER_SANITIZE_NUMBER_INT) : 0, + "cancelled" => 0 + ], + "address" => [ + "type" => "PostalAddress", + "value" => [ + "addressLocality" => filter_input(INPUT_POST, "addressLocality", FILTER_SANITIZE_STRING), + "postalCode" => filter_input(INPUT_POST, "postalCode", FILTER_SANITIZE_STRING), + "streetAddress" => filter_input(INPUT_POST, "streetAddress", FILTER_SANITIZE_STRING) + ] + ], + "keys" => [ + "public" => $pubKey, + "private" => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), + "nfc" => filter_input(INPUT_POST, "nfc", FILTER_SANITIZE_STRING), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "blockchain" => [ + "address" => $newBcUser, + "password" => $this->_GeniSys->_helpers->oEncrypt($bcPass), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "mqtt" => [ + "username" => $this->_GeniSys->_helpers->oEncrypt($mqttUser), + "password" => $this->_GeniSys->_helpers->oEncrypt($mqttPass), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "coap" => [ + "username" => "", + "password" => "" + ], + "amqp" => [ + "username" => $this->_GeniSys->_helpers->oEncrypt($amqppubKey), + "password" => $this->_GeniSys->_helpers->oEncrypt($amqpprvKey), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateCreated" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateFirstUsed" => [ + "type" => "DateTime", + "value" => "" + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; - $actionMsg = ""; - $balanceMessage = ""; + $response = json_decode($this->contextBrokerRequest("POST", $this->cb["entities_url"] . "?type=Patient", $this->createContextHeaders(), json_encode($data)), true); + if($response["Response"]=="OK"): + + + $hash = ""; + $this->pcontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["pcontract"]))->send("registerPatient", $pubKey, $newBcUser, $active, $admitted, $discharged, $pid, $pid, time(), $_SESSION["GeniSysAI"]["Uid"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash) { + if ($err !== null) { + $hash = "FAILED"; + return; + } + $hash = $resp; + }); + + $actionMsg = ""; + $balanceMessage = ""; + + if($hash == "FAILED"): + $actionMsg = " HIAS Blockchain registerPatient failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("Register Patient", $hash, $pid); + $this->storeUserHistory("Register Patient", $txid, $pid); + $balance = $this->getBlockchainBalance(); + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!\n"; + endif; - if($hash == "FAILED"): - $actionMsg = " HIAS Blockchain registerPatient failed!"; - else: - $txid = $this->storeBlockchainTransaction("Register Patient", $hash, $pid); - $this->storeUserHistory("Register Patient", $txid, $pid); - $balance = $this->getBlockchainBalance(); - $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; - endif; + $this->icontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["icontract"]))->send("registerAuthorized", $newBcUser, ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg .= " HIAS Blockchain registerAuthorized failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("iotJumpWay Register Authorized", $hash, $pid); + $this->storeUserHistory("Register Authorized", $txid, $pid); + $balance = $this->getBlockchainBalance(); + if($balanceMessage == ""): + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!\n"; + endif; + endif; - $this->icontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["icontract"]))->send("registerAuthorized", $newBcUser, ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { - if ($err !== null) { - $hash = "FAILED"; - $msg = $err; - return; - } - $hash = $resp; - }); + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttu ( + `lid`, + `pid`, + `uname`, + `pw` + ) VALUES ( + :lid, + :pid, + :uname, + :pw + ) + "); + $query->execute([ + ':lid' => $lid, + ':pid' => $pid, + ':uname' => $mqttUser, + ':pw' => $mqttHash + ]); + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttua ( + `lid`, + `pid`, + `username`, + `topic`, + `rw` + ) VALUES ( + :lid, + :pid, + :username, + :topic, + :rw + ) + "); + $query->execute(array( + ':lid' => $lid, + ':pid' => $pid, + ':username' => $mqttUser, + ':topic' => $location["context"]["Data"]["id"]."/Patients/#", + ':rw' => 4 + )); - if($hash == "FAILED"): - $actionMsg .= " HIAS Blockchain registerAuthorized failed! " . $msg; + return [ + "Response"=> "OK", + "Message" => "Patient created! " . $actionMsg . $balanceMessage, + "UID" => $pid, + "Uname" => filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING), + "Upass" => $uPass, + "AppID" => $pubKey, + "AppKey" => $privKey, + "BCU" => $newBcUser, + "BCP" => $bcPass, + "MU" => $mqttUser, + "MP" => $mqttPass + ]; else: - $txid = $this->storeBlockchainTransaction("iotJumpWay Register Authorized", $hash, $pid); - $this->storeUserHistory("Register Authorized", $txid, $pid); - $balance = $this->getBlockchainBalance(); - if($balanceMessage == ""): - $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; - endif; + return [ + "Response" => "FAILED", + "Patient create failed!" + ]; endif; - - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE patients - SET aid = :aid - WHERE id = :id - "); - $query->execute(array( - ':aid'=> $aid, - ':id'=> $pid - )); - - $query = $this->_GeniSys->_secCon->prepare(" - INSERT INTO mqttu ( - `lid`, - `pid`, - `aid`, - `uname`, - `pw` - ) VALUES ( - :lid, - :pid, - :aid, - :uname, - :pw - ) - "); - $query->execute([ - ':lid' => $lid, - ':pid' => $pid, - ':aid' => $aid, - ':uname' => $mqttUser, - ':pw' => $mqttHash - ]); - - $query = $this->_GeniSys->_secCon->prepare(" - INSERT INTO mqttua ( - `lid`, - `pid`, - `aid`, - `username`, - `topic`, - `rw` - ) VALUES ( - :lid, - :pid, - :aid, - :username, - :topic, - :rw - ) - "); - $query->execute(array( - ':lid' => $lid, - ':pid' => $pid, - ':aid' => $aid, - ':username' => $mqttUser, - ':topic' => $lid."/Patients/#", - ':rw' => 4 - )); - - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE mqttl - SET apps = apps + 1 - WHERE id = :id - "); - $query->execute(array( - ':id'=>$lid - )); - - return [ - "Response"=> "OK", - "Message" => "Patient & application created! " . $actionMsg . $balanceMessage, - "UID" => $pid, - "Uname" => filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING), - "Upass" => $uPass, - "AppID" => $pubKey, - "AppKey" => $privKey, - "BCU" => $newBcUser, - "BCP" => $bcPass, - "MU" => $mqttUser, - "MP" => $mqttPass - ]; } public function updatePatient() { - if(!filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT)): + + if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): return [ "Response"=> "Failed", - "Message" => "ID is required" + "Message" => "iotJumpWay location id is required" ]; endif; + + if(!$this->checkLocation(filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location does not exist" + ]; + endif; + if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): return [ "Response"=> "Failed", - "Message" => "Patient name is required" + "Message" => "Staff name is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Staff username is required" ]; endif; if(!filter_input(INPUT_POST, "email", FILTER_SANITIZE_EMAIL)): @@ -659,212 +833,331 @@ public function updatePatient() ]; endif; - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT id - FROM patients - WHERE email = :email - "); - $pdoQuery->execute([ - ":email" => filter_input(INPUT_POST, "email", FILTER_SANITIZE_EMAIL) - ]); - $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); - - if($response["id"] && $response["id"] != filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT)): + if(!filter_input(INPUT_POST, "streetAddress", FILTER_SANITIZE_STRING)): return [ "Response"=> "Failed", - "Message" => "Patient email exists" + "Message" => "Location street address is required" ]; endif; - if(!filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING)): + if(!filter_input(INPUT_POST, "addressLocality", FILTER_SANITIZE_STRING)): return [ "Response"=> "Failed", - "Message" => "Patient username is required" + "Message" => "Location address locality is required" ]; endif; - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT id - FROM patients - WHERE username = :username - "); - $pdoQuery->execute([ - ":username" => filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING) - ]); - $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + if(!filter_input(INPUT_POST, "postalCode", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Location postal code is required" + ]; + endif; - if($response["id"] && $response["id"] != filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT)): + if(!filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING)): return [ "Response"=> "Failed", - "Message" => "Patient username exists" + "Message" => "Patient username is required" ]; endif; - if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): + $pid = filter_input(INPUT_GET, "patient", FILTER_SANITIZE_NUMBER_INT); + $patient = $this->getPatient($pid); + + if($patient["context"]["Data"]["status"]["cancelled"]): return [ "Response"=> "Failed", - "Message" => "iotJumpWay location id is required" + "Message" => "This patient is cancelled, to allow access again you must create a new patient." ]; endif; + $lid = filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT); + $location = $this->getLocation($lid); + $name = filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING); $username = filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING); + $email = filter_input(INPUT_POST, "email", FILTER_SANITIZE_STRING); + $active = filter_input(INPUT_POST, "active", FILTER_SANITIZE_NUMBER_INT) ? True : False; $admitted = filter_input(INPUT_POST, "admitted", FILTER_SANITIZE_NUMBER_INT) ? True : False; - $discharged = filter_input(INPUT_POST, "discharged", FILTER_SANITIZE_NUMBER_INT) ? True : False; - $aid = filter_input(INPUT_POST, "aid", FILTER_SANITIZE_NUMBER_INT); - $pid = filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT); + $discharged = filter_input(INPUT_POST, "admitted", FILTER_SANITIZE_NUMBER_INT) ? False : True; + + $data = [ + "category" => [ + "value" => [filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)] + ], + "name" => [ + "value" => $name + ], + "username" => [ + "value" => $username + ], + "description" => [ + "value" => filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING) + ], + "email" => [ + "value" => filter_input(INPUT_POST, "email", FILTER_SANITIZE_STRING) + ], + "nfc" => [ + "value" => filter_input(INPUT_POST, "nfc", FILTER_SANITIZE_STRING) + ], + "picture" => [ + "value" => "default.png" + ], + "lid" => [ + "value" => $lid, + "entity" => $location["context"]["Data"]["id"] + ], + "status" => [ + "online" => "OFFLINE", + "active" => filter_input(INPUT_POST, "active", FILTER_SANITIZE_NUMBER_INT) ? filter_input(INPUT_POST, "active", FILTER_SANITIZE_NUMBER_INT) : 0, + "admitted" => filter_input(INPUT_POST, "admitted", FILTER_SANITIZE_NUMBER_INT) ? filter_input(INPUT_POST, "admitted", FILTER_SANITIZE_NUMBER_INT) : 0, + "cancelled" => filter_input(INPUT_POST, "cancelled", FILTER_SANITIZE_NUMBER_INT) ? filter_input(INPUT_POST, "cancelled", FILTER_SANITIZE_NUMBER_INT) : 0 + ], + "address" => [ + "type" => "PostalAddress", + "value" => [ + "addressLocality" => filter_input(INPUT_POST, "addressLocality", FILTER_SANITIZE_STRING), + "postalCode" => filter_input(INPUT_POST, "postalCode", FILTER_SANITIZE_STRING), + "streetAddress" => filter_input(INPUT_POST, "streetAddress", FILTER_SANITIZE_STRING) + ] + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - UPDATE patients - SET name = :name, - username = :username, - email = :email, - active = :active, - admitted = :admitted, - discharged = :discharged - WHERE id = :id - "); - $pdoQuery->execute([ - ":name" => $name, - ":username" => $username, - ":email" => $email, - ":active" => filter_input(INPUT_POST, "active", FILTER_SANITIZE_NUMBER_INT) ? 1 : 0, - ":admitted" => filter_input(INPUT_POST, "admitted", FILTER_SANITIZE_NUMBER_INT) ? 1 : 0, - ":discharged" => filter_input(INPUT_POST, "discharged", FILTER_SANITIZE_NUMBER_INT) ? 1 : 0, - ":id" => $pid - ]); - $pdoQuery->closeCursor(); - $pdoQuery = null; + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $patient["context"]["Data"]["id"] . "/attrs?type=Patient", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + UPDATE patients + SET username = :username + WHERE id = :id + "); + $pdoQuery->execute([ + ":username" => $username, + ":id" => $pid + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + $unlocked = $this->unlockBlockchainAccount(); + + if($unlocked == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Unlocking HIAS Blockhain Account Failed!" + ]; + endif; - $unlocked = $this->unlockBlockchainAccount(); + $hash = ""; + $this->pcontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["pcontract"]))->send("updatePatient", $patient["context"]["Data"]["id"], $patient["context"]["Data"]["blockchain"]["address"], $active, $admitted, $discharged, $pid, $pid, time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash) { + if ($err !== null) { + $hash = "FAILED"; + return; + } + $hash = $resp; + }); + + $actionMsg = ""; + $balanceMessage = ""; + + if($hash == "FAILED"): + $actionMsg = " HIAS Blockchain updatePatient failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("Update Patient", $hash, $pid); + $this->storeUserHistory("Update Patient", $txid, $pid); + $balance = $this->getBlockchainBalance(); + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!\n"; + endif; + + if(!$patient["context"]["Data"]["status"]["cancelled"] && filter_input(INPUT_POST, "cancelled", FILTER_SANITIZE_STRING)): + + $query = $this->_GeniSys->_secCon->prepare(" + DELETE FROM mqttu + WHERE pid = :pid + "); + $query->execute([ + ':pid' => $pid + ]); + + $query = $this->_GeniSys->_secCon->prepare(" + DELETE FROM mqttua + WHERE aid = :aid + "); + $query->execute([ + ':aid' => $pid + ]); + + $this->pcontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["pcontract"]))->send("deregisterUser", $patient["context"]["Data"]["id"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg .= " HIAS Blockchain patients deregisterAuthorized failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("Deregister Patient", $hash, $pid); + $this->storeUserHistory("Deregister Patient", $txid, $pid); + $balance = $this->getBlockchainBalance(); + if($balanceMessage == ""): + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!\n"; + endif; + endif; + + endif; - if($unlocked == "FAILED"): return [ - "Response"=> "Failed", - "Message" => "Unlocking HIAS Blockhain Account Failed!" + "Response"=> "OK", + "Message" => "Patient updated!" . $actionMsg . $balanceMessage + ]; + else: + return [ + "Response"=> "FAILED", + "Message" => "Patient update failed!" ]; endif; + } - $hash = ""; - $this->pcontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["pcontract"]))->send("updatePatient", filter_input(INPUT_POST, "identifier", FILTER_SANITIZE_STRING), filter_input(INPUT_POST, "bcu", FILTER_SANITIZE_STRING), $active, $admitted, $discharged, $pid, $aid, time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash) { - if ($err !== null) { - $hash = "FAILED"; - return; - } - $hash = $resp; - }); + public function resetMqtt() + { + $mqttPass = $this->_GeniSys->_helpers->generateKey(32); + $mqttHash = create_hash($mqttPass); - $actionMsg = ""; - $balanceMessage = ""; + $pid = filter_input(INPUT_GET, 'patient', FILTER_SANITIZE_NUMBER_INT); + $patient = $this->getPatient($pid); + + $data = [ + "mqtt" => [ + "username" => $patient["context"]["Data"]["mqtt"]["username"], + "password" => $this->_GeniSys->_helpers->oEncrypt($mqttPass), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; - if($hash == "FAILED"): - $actionMsg = " HIAS Blockchain updatePatient failed!"; + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $patient["context"]["Data"]["id"] . "/attrs?type=Patient", $this->createContextHeaders(), json_encode($data)), true); + if($response["Response"]=="OK"): + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttu + SET pw = :pw + WHERE pid = :pid + "); + $query->execute(array( + ':pw' => $mqttHash, + ':pid' => $patient["context"]["Data"]["pid"]["value"] + )); + + $this->storeUserHistory("Reset User MQTT Password", 0, $pid); + + return [ + "Response"=> "OK", + "Message" => "MQTT password reset!", + "P" => $mqttPass + ]; else: - $txid = $this->storeBlockchainTransaction("Update Patient", $hash, $pid); - $this->storeUserHistory("Update Patient", $txid, $pid); - $balance = $this->getBlockchainBalance(); - $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; + return [ + "Response"=> "FAILED", + "Message" => "MQTT password reset failed!" + ]; endif; - return [ - "Response"=> "OK", - "Message" => "Patient updated!" . $actionMsg . $balanceMessage - ]; } - public function resetMqtt() + public function resetAppAmqpKey() { - $mqttPass = $this->_GeniSys->_helpers->password(); - $mqttHash = create_hash($mqttPass); + $pid = filter_input(INPUT_GET, 'patient', FILTER_SANITIZE_NUMBER_INT); + $patient = $this->getPatient($pid); + + $amqpPass = $this->_GeniSys->_helpers->generateKey(32); + $amqpHash = $this->_GeniSys->_helpers->createPasswordHash($amqpPass); + + $data = [ + "amqp" => [ + "username" => $patient["context"]["Data"]["amqp"]["username"], + "password" => $this->_GeniSys->_helpers->oEncrypt($amqpPass), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE mqtta - SET mqttp = :mqttp - WHERE id = :id - "); - $query->execute([ - ':mqttp' => $this->_GeniSys->_helpers->oEncrypt($mqttPass), - ':id' => filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT) - ]); + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $patient["context"]["Data"]["id"] . "/attrs?type=Patient", $this->createContextHeaders(), json_encode($data)), true); - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE mqttu - SET pw = :pw - WHERE aid = :aid - "); - $query->execute([ - ':pw' => $mqttHash, - ':aid' => filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT) - ]); + if($response["Response"]=="OK"): - $this->storeUserHistory("Reset Patient MQTT Password", 0, filter_input(INPUT_POST, "pid", FILTER_SANITIZE_STRING)); + $this->storeUserHistory("Reset Patient Application AMQP Password", 0, $pid); - return [ - "Response"=> "OK", - "Message" => "MQTT password reset!", - "P" => $mqttPass - ]; + return [ + "Response"=> "OK", + "Message" => "AMQP password reset!", + "P" => $amqpPass + ]; + else: + return [ + "Response"=> "FAILED", + "Message" => "AMQP password reset failed!" + ]; + endif; } public function resetAppKey() { - $identifier = filter_input(INPUT_POST, "identifier", FILTER_SANITIZE_STRING); - $pid = filter_input(INPUT_POST, "pid", FILTER_SANITIZE_NUMBER_INT); - $id = filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT); + + $pid = filter_input(INPUT_GET, 'patient', FILTER_SANITIZE_NUMBER_INT); + $patient = $this->getPatient($pid); $privKey = $this->_GeniSys->_helpers->generateKey(32); $privKeyHash = $this->_GeniSys->_helpers->createPasswordHash($privKey); $htpasswd = new Htpasswd('/etc/nginx/security/patients'); - $htpasswd->updateUser($identifier, $privKey, Htpasswd::ENCTYPE_APR_MD5); - - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE mqtta - SET aprv = :aprv - WHERE id = :id - "); - $query->execute(array( - ':aprv' => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), - ':id' => $id - )); - - $this->storeUserHistory("Reset Patient Key", 0, filter_input(INPUT_POST, "pid", FILTER_SANITIZE_STRING)); - - return [ - "Response"=> "OK", - "Message" => "Application key reset!", - "P" => $privKey + $htpasswd->updateUser($patient["context"]["Data"]["id"], $privKey, Htpasswd::ENCTYPE_APR_MD5); + + $data = [ + "keys" => [ + "public" => $patient["context"]["Data"]["keys"]["public"], + "private" => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] ]; - } + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $patient["context"]["Data"]["id"] . "/attrs?type=Patient", $this->createContextHeaders(), json_encode($data)), true); - public function getMapMarkers($application) - { - if(!$application["lt"]): - $lat = $this->_GeniSys->lt; - $lng = $this->_GeniSys->lg; - else: - $lat = $application["lt"]; - $lng = $application["lg"]; - endif; + if($response["Response"]=="OK"): - return [$lat, $lng]; - } + $this->storeUserHistory("Reset Patient Application Key", 0, $pid); + + return [ + "Response"=> "OK", + "Message" => "App key reset!", + "P" => $privKey + ]; - public function getStatusShow($status) - { - if($status=="ONLINE"): - $on = " "; - $off = " hide "; else: - $on = " hide "; - $off = " "; + return [ + "Response"=> "FAILED", + "Message" => "App key reset failed!" + ]; endif; - return [$on, $off]; } } @@ -874,12 +1167,19 @@ public function getStatusShow($status) if(filter_input(INPUT_POST, "create_patient", FILTER_SANITIZE_NUMBER_INT)): die(json_encode($Patients->createPatient())); endif; + if(filter_input(INPUT_POST, "update_patient", FILTER_SANITIZE_NUMBER_INT)): die(json_encode($Patients->updatePatient())); endif; + if(filter_input(INPUT_POST, "reset_mqtt_patient", FILTER_SANITIZE_NUMBER_INT)): die(json_encode($Patients->resetMqtt())); endif; + if(filter_input(INPUT_POST, "reset_pt_apriv", FILTER_SANITIZE_NUMBER_INT)): die(json_encode($Patients->resetAppKey())); + endif; + + if(filter_input(INPUT_POST, "reset_patient_amqp", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($Patients->resetAppAmqpKey())); endif; \ No newline at end of file diff --git a/Root/var/www/html/Hospital/Patients/Create.php b/Root/var/www/html/Hospital/Patients/Create.php index 74f828f..da33b01 100644 --- a/Root/var/www/html/Hospital/Patients/Create.php +++ b/Root/var/www/html/Hospital/Patients/Create.php @@ -1,8 +1,8 @@ "HIS", - "SubPageID" => "Patients" + "PageID" => "HIS", + "SubPageID" => "Patients" ]; include dirname(__FILE__) . '/../../../Classes/Core/init.php'; @@ -20,155 +20,211 @@ - - - - - <?=$_GeniSys->_confs["meta_title"]; ?> - " /> - - - - - - - - - - - - - - - + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + -
-
-
- -
- - - - - -
-
- - - -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-
-
Create Hospital Patient -
-
-
-
-
-
-
-
-
-
-
-
-
- - - Name of patient -
-
- - - Email of patient -
-
- - - Username of patient -
-
- - -
-
-
-
- - - Location of patient -
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
- - - -
- - - - - - - - +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
Create Hospital Patient +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + Name of patient +
+
+ + + Patient description +
+
+ + + Username of patient +
+
+ + + Patient category +
+
+ + + Email of patient +
+
+ + + iotJumpWay Location street address +
+
+ + + iotJumpWay Location address locality +
+
+ + + iotJumpWay Location post code +
+
+ + + UID of patient's NFC card/fob/implant +
+
+ + +
+
+
+
+ + + Location of patient +
+
+ + + Is patient active? +
+
+ + + Is patient admitted? +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + +
+ + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/Hospital/Patients/Media/Images/Uploads/Adam-Milton-Barker.png b/Root/var/www/html/Hospital/Patients/Media/Images/Uploads/Adam-Milton-Barker.png deleted file mode 100644 index b4fff9daaa7e6e76c2bb6d67e4dce86dad8368a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 470001 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 z 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�Ag)xe-27LmYl72s`MvC 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�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 $Locations = $iotJumpWay->getLocations(0, "id ASC"); -$Zones = $iotJumpWay->getZones(0, "id ASC"); -$Devices = $iotJumpWay->getDevices(0, "id ASC"); -$Applications = $iotJumpWay->getApplications(0, "id ASC"); $PId = filter_input(INPUT_GET, 'patient', FILTER_SANITIZE_NUMBER_INT); $Patient = $Patients->getPatient($PId); -list($lat, $lng) = $Patients->getMapMarkers($Patient); -list($on, $off) = $Patients->getStatusShow($Patient["status"]); +$cancelled = $Patient["context"]["Data"]["status"]["cancelled"] ? True : False; ?> @@ -48,7 +44,7 @@ - + @@ -112,31 +108,31 @@

+
+
+ + + Photo of patient
- - > $value): + $Locations = $iotJumpWay->getLocations(); + if(count($Locations["Data"])): + foreach($Locations["Data"] as $key => $value): ?> - + - iotJumpWay application -
-
- - "> - "> - "> - "> - -
-
-
-
- - - Photo of patient + Location of patient
- /> + /> Is Patient Active?
- /> + /> Is Patient Admitted?
- - /> - Is Patient Discharged? + + > + Is staff member cancelled?
@@ -211,7 +230,7 @@
Patient History
-
+
@@ -231,7 +250,7 @@ retrieveHistory($Patient["id"], 5); + $history = $Patients->retrieveHistory($Patient["context"]["Data"]["pid"]["value"], 5); if(count($history)): foreach($history as $key => $value): if($value["uid"]): @@ -248,7 +267,7 @@ - /Transaction/"># + /Transaction/"># @@ -280,7 +299,7 @@
Patient Transactions
- +
@@ -299,7 +318,7 @@ retrieveTransactions($Patient["id"], 5); + $transactions = $Patients->retrieveTransactions($Patient["context"]["Data"]["pid"]["value"], 5); if(count($transactions)): foreach($transactions as $key => $value): if($value["uid"]): @@ -311,7 +330,7 @@ # - /Transaction/"># + /Transaction/"># @@ -329,40 +348,14 @@
-
-
-
-
Patient Application #
-
-
Online Offline
-
-
-
-
- -
-  %    -  %    -  %    -  °C -
-
-
-
- " style="width: 100%; !important;" /> -
-
-
-
-
-
-
+ " style="width: 100%; !important;" />
+
@@ -370,7 +363,7 @@
-

+

@@ -383,7 +376,7 @@
-

_helpers->oDecrypt($Patient["bcaddress"]); ?>

+

@@ -397,19 +390,41 @@ class="fa fa-refresh">
Reset MQTT Password
-

_helpers->oDecrypt($Patient["mqttu"]); ?>

+

_helpers->oDecrypt($Patient["context"]["Data"]["mqtt"]["username"]); ?>

-

_helpers->oDecrypt($Patient["mqttp"]); ?> +

_helpers->oDecrypt($Patient["context"]["Data"]["mqtt"]["password"]); ?>

+
+
+
+ +
+ +
+

_helpers->oDecrypt($Patient["context"]["Data"]["amqp"]["username"]) : ""; ?>

+
+
+
+ +
+

_helpers->oDecrypt($Patient["context"]["Data"]["amqp"]["password"]) : ""; ?> +

Last Updated:

+

+
+
+
+
+
+
@@ -423,32 +438,13 @@ class="fa fa-refresh"> Reset MQTT Password
- - \ No newline at end of file diff --git a/Root/var/www/html/Hospital/Patients/Transaction.php b/Root/var/www/html/Hospital/Patients/Transaction.php index aa254e8..3501e03 100644 --- a/Root/var/www/html/Hospital/Patients/Transaction.php +++ b/Root/var/www/html/Hospital/Patients/Transaction.php @@ -18,9 +18,6 @@ $txn = $iotJumpWay->retrieveDeviceTransaction(filter_input(INPUT_GET, 'transaction', FILTER_SANITIZE_NUMBER_INT)); $receipt = $iotJumpWay->retrieveDeviceTransactionReceipt($iotJumpWay->_GeniSys->_helpers->oDecrypt($txn["hash"])); -list($lat, $lng) = $Patients->getMapMarkers($Patient); -list($on, $off) = $Patients->getStatusShow($Patient["status"]); - ?> @@ -140,22 +137,6 @@
-
-
-
-
Online Offline
-
- -
-  %    -  %    -  %    -  °C -
-
-
-
-
@@ -169,7 +150,6 @@ - \ No newline at end of file diff --git a/Root/var/www/html/Hospital/Patients/index.php b/Root/var/www/html/Hospital/Patients/index.php index 3f481a1..30bfa36 100644 --- a/Root/var/www/html/Hospital/Patients/index.php +++ b/Root/var/www/html/Hospital/Patients/index.php @@ -10,7 +10,6 @@ include dirname(__FILE__) . '/../../Hospital/Patients/Classes/Patients.php'; $_GeniSysAi->checkSession(); -$Patients = $Patients->getPatients(); ?> @@ -37,7 +36,7 @@ - + @@ -82,7 +81,23 @@
-
+
+
+
+
+
HIAS Patients
+
+
+
+
+
+
+ +

The HIAS patients area allows you to create and manage patinent accounts. Each user has a connected iotJumpWay application that provides the creditials and permissions to access the network and store data in the HIAS Blockchain. The patients network is a work in progress and is separate to the core network that HIAS staff, devices and applications have access to.

+ +
+
+
@@ -108,22 +123,23 @@ $value): + $Patients = $Patients->getPatients(); + if($Patients["Response"] != "Failed"): + foreach($Patients["Data"] as $key => $value): ?> # - " style="max-width: 100px; !important;" /> + " style="max-width: 100px; !important;" /> - Name: + Name: -
"> - +
"> +
- "> Edit + "> Edit
+
diff --git a/Root/var/www/html/Hospital/Staff/API/Applications/NLU/Classes/Auth.php b/Root/var/www/html/Hospital/Staff/API/Applications/NLU/Classes/Auth.php deleted file mode 100644 index 69e0aa2..0000000 --- a/Root/var/www/html/Hospital/Staff/API/Applications/NLU/Classes/Auth.php +++ /dev/null @@ -1,165 +0,0 @@ -_Method = $_SERVER['REQUEST_METHOD']; - $this->_Args = explode('/', rtrim($_REQUEST['params'], '/')); - $this->_Endpoint = array_shift($this->_Args); - - if (array_key_exists(0,$this->_Args) && !is_numeric($this->_Args[0])): - $this->_Verb = array_shift($this->_Args); - endif; - - if ($this->_Method == 'POST' && array_key_exists('HTTP_X_HTTP_METHOD', $_SERVER)): - if ($_SERVER['HTTP_X_HTTP_METHOD'] == 'DELETE'): - $this->_Method = 'DELETE'; - elseif($_SERVER['HTTP_X_HTTP_METHOD'] == 'PUT'): - $this->_Method = 'PUT'; - else: - $this->_response('Unexpected Header', 401); - endif; - endif; - - switch($this->_Method): - case 'DELETE': - $this->invalidMethod = True; - case 'POST': - if($_SERVER['CONTENT_TYPE']=='application/json'): - $_POST = json_decode(file_get_contents('php://input'), true); - elseif($_SERVER['CONTENT_TYPE']=='application/x-www-form-urlencoded'): - $_POST = json_decode(file_get_contents("php://input"), true); - endif; - break; - case 'GET': - $this->invalidMethod = True; - break; - case 'PUT': - $this->invalidMethod = True; - break; - default: - $this->invalidMethod = True; - break; - endswitch; - } - - private function _cleanInputs($data) - { - $clean_input = Array(); - if (is_array($data)) { - foreach ($data as $k => $v) { - $clean_input[$k] = $this->_cleanInputs($v); - } - } else { - $clean_input = trim(strip_tags($data)); - } - return $clean_input; - - } - - private function getAuthHeaders(){ - - $this->writeFile("api.txt",[ - "p"=>$_SERVER["PHP_AUTH_USER"], - "pv"=>$_SERVER["PHP_AUTH_USER"], - "a"=>$_SERVER - ]); - if(!isSet($_SERVER["PHP_AUTH_USER"]) || !isSet($_SERVER["PHP_AUTH_PW"])): - return False; - endif; - $this->appPub = $_SERVER["PHP_AUTH_USER"]; - $this->appPrv = $_SERVER["PHP_AUTH_PW"]; - - $this->writeFile( - "auth.txt", - $this->appPub, - $this->appPrv); - } - - private function checkAuth($public, $private) - { - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT aprv - FROM mqtta - WHERE apub = :apub - "); - $pdoQuery->execute([ - ":apub" => $public - ]); - $user=$pdoQuery->fetch(PDO::FETCH_ASSOC); - - if($this->verifyPassword($private, $this->_GeniSys->_helpers->oDecrypt($user["aprv"]))): - return True; - else: - return False; - endif; - } - - protected static function verifyPassword($password,$hash) { - return password_verify($password, $hash); - } - - public function process() - { - if($this->invalidMethod): - return $this->_response(["Response"=>"FAILED","Message"=>"Invalid Method"], 405); - endif; - - $this->getAuthHeaders(); - - if (!$this->appPub || !$this->appPrv): - return $this->_response(["Response"=>"FAILED","Message"=>"No Authorisation Provided"], 401); - endif; - - if(!$this->checkAuth($this->appPub, $this->appPrv)): - return $this->_response(["Response"=>"FAILED","Message"=>"Invalid Authorisation Provided"], 401); - endif; - - if ((int)method_exists($this, $this->_Endpoint) > 0): - return $this->_response($this->{$this->_Endpoint}($this->_Args)); - endif; - - return $this->_response("No Endpoint: ".$this->_Endpoint, 404); - - } - - private function _requestStatus($code) { - - $status = [ - 200 => 'OK', - 401 => 'Not Authorized', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 500 => 'Internal Server Error', - ]; - - return ($status[$code])?$status[$code]:$status[500]; - - } - - private function _response($data, $status = 200) - { - header("HTTP/1.1 " . $status . " " . $this->_requestStatus($status)); - return json_encode($data); - } - - protected function writeFile($file,$data) - { - $fps = fopen($file, 'w'); - fwrite($fps, print_r($data, TRUE)); - fclose($fps); - } - } diff --git a/Root/var/www/html/Hospital/Staff/API/Applications/NLU/index.php b/Root/var/www/html/Hospital/Staff/API/Applications/NLU/index.php deleted file mode 100644 index c06e68e..0000000 --- a/Root/var/www/html/Hospital/Staff/API/Applications/NLU/index.php +++ /dev/null @@ -1,173 +0,0 @@ - "HIS", - "SubPageID" => "API", - "LowPageID" => "NLU" -]; - -include dirname(__FILE__) . '/../../../../../../Classes/Core/init.php'; -include dirname(__FILE__) . '/../../../../../../Classes/Core/GeniSys.php'; - -require_once 'Classes/Auth.php'; - -class NLU extends Auth{ - - protected $_GeniSys; - - public function __construct($_GeniSys) - { - parent::__construct(); - $this->_GeniSys = $_GeniSys; - } - - public function Infer() - { - if(!isset($_POST["query"])): - return [ - "Response"=>"FAILED", - "Message"=>"Query must be provided" - ]; - endif; - - if(!isset($_POST["uid"])): - return [ - "Response"=>"FAILED", - "Message"=>"User ID must be provided" - ]; - endif; - - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT users.id, - users.name, - users.cz, - users.czt - FROM users users - WHERE users.id = :id - "); - $pdoQuery->execute([ - ":id" => $_POST["uid"] - ]); - $user=$pdoQuery->fetch(PDO::FETCH_ASSOC); - - if(!$user["id"]): - return [ - "Response"=>"FAILED", - "Message"=>"Invalid user" - ]; - endif; - - $basicAuth = $this->appPub . ":" . $this->appPrv; - $basicAuth = base64_encode($basicAuth); - - $headers = [ - "Content-Type: application/json", - 'Authorization: Basic '. $basicAuth - ]; - - if($user["czt"] > time() - 5 * 60): - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT genisysainlu.id, - genisysainlu.lid, - genisysainlu.zid, - genisysainlu.did, - mqttld.status - FROM genisysainlu genisysainlu - INNER JOIN mqttld mqttld - ON mqttld.id = genisysainlu.did - WHERE genisysainlu.zid = :id - && mqttld.status = :status - "); - $pdoQuery->execute([ - ":id" => $user["cz"], - ":status" => "ONLINE" - ]); - $nlu=$pdoQuery->fetch(PDO::FETCH_ASSOC); - - if($nlu["id"] != null): - $endpoint = "Audio"; - $audio = true; - else: - $nlu = $this->checkNLU(); - if($nlu["id"] != null): - $endpoint = "Api"; - $audio = false; - else: - return [ - "Response" => "FAILED", - "Message" => "There are no active Natural Language Understanding Engines in your location!" - ]; - endif; - endif; - else: - $nlu = $this->checkNLU(); - if($nlu["id"] != null): - $endpoint = "Api"; - $audio = false; - else: - return [ - "Response" => "FAILED", - "Message" => "There are no active Natural Language Understanding Engines in your location!" - ]; - endif; - endif; - - $response = $this->apiCall("POST", $this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/GeniSysAI/NLU/API/" . $endpoint, $headers, json_encode(["user" => $_POST["uid"], "query" => $_POST["query"]])); - - return [ - "Response" => "OK", - "Message" => "NLU request successful!", - "Audio" => $audio, - "Data" => json_decode($response, true) - ]; - } - - private function checkNLU() - { - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT genisysainlu.id, - genisysainlu.lid, - genisysainlu.zid, - genisysainlu.did - FROM genisysainlu genisysainlu - INNER JOIN mqttld mqttld - ON mqttld.id = genisysainlu.did - WHERE mqttld.status = :status - "); - $pdoQuery->execute([ - ":status" => "ONLINE" - ]); - $nlu=$pdoQuery->fetch(PDO::FETCH_ASSOC); - return $nlu; - } - - private function apiCall($method, $url, $headers, $json) - { - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_HEADER, 1); - curl_setopt($ch, CURLOPT_TIMEOUT, 30); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); - curl_setopt($ch, CURLOPT_POSTFIELDS, $json); - $response = curl_exec($ch); - $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); - $header = substr($response, 0, $header_size); - $body = substr($response, $header_size); - curl_close($ch); - - $parsedText = str_replace(chr(10), "", $body); - $parsedText = str_replace(chr(13), "", $parsedText); - - return $parsedText; - } -} - -try { - $NLU = new NLU($_GeniSys); - echo $NLU->process(); -} catch (Exception $e) { - echo json_encode([ - 'error' => $e->getMessage() - ]); -} \ No newline at end of file diff --git a/Root/var/www/html/Hospital/Staff/API/Applications/User/Classes/Auth.php b/Root/var/www/html/Hospital/Staff/API/Applications/User/Classes/Auth.php deleted file mode 100644 index e4d367a..0000000 --- a/Root/var/www/html/Hospital/Staff/API/Applications/User/Classes/Auth.php +++ /dev/null @@ -1,160 +0,0 @@ -_Method = $_SERVER['REQUEST_METHOD']; - $this->_Args = explode('/', rtrim($_REQUEST['params'], '/')); - $this->_Endpoint = array_shift($this->_Args); - - if (array_key_exists(0,$this->_Args) && !is_numeric($this->_Args[0])): - $this->_Verb = array_shift($this->_Args); - endif; - - if ($this->_Method == 'POST' && array_key_exists('HTTP_X_HTTP_METHOD', $_SERVER)): - if ($_SERVER['HTTP_X_HTTP_METHOD'] == 'DELETE'): - $this->_Method = 'DELETE'; - elseif($_SERVER['HTTP_X_HTTP_METHOD'] == 'PUT'): - $this->_Method = 'PUT'; - else: - $this->_response('Unexpected Header', 401); - endif; - endif; - - switch($this->_Method): - case 'DELETE': - $this->invalidMethod = True; - case 'POST': - if($_SERVER['CONTENT_TYPE']=='application/json'): - $_POST = json_decode(file_get_contents('php://input'), true); - if($this->debug): - $this->writeFile("type.txt", ["Server","JSON"]); - endif; - elseif($_SERVER['CONTENT_TYPE']=='application/x-www-form-urlencoded'): - $_POST = json_decode(file_get_contents("php://input"), true); - if($this->debug): - $this->writeFile("type.txt", ["Server","URL"]); - endif; - endif; - break; - case 'GET': - $this->invalidMethod = True; - break; - case 'PUT': - $this->invalidMethod = True; - break; - default: - $this->invalidMethod = True; - break; - endswitch; - } - - private function _cleanInputs($data) - { - $clean_input = Array(); - if (is_array($data)) { - foreach ($data as $k => $v) { - $clean_input[$k] = $this->_cleanInputs($v); - } - } else { - $clean_input = trim(strip_tags($data)); - } - return $clean_input; - - } - - protected static function verifyPassword($password, $hash) { - return password_verify($password, $hash); - } - - private function getAuthHeaders(){ - if(!isSet($_SERVER["PHP_AUTH_USER"]) || !isSet($_SERVER["PHP_AUTH_PW"])): - return False; - endif; - $authParts=[$_SERVER["PHP_AUTH_USER"], $_SERVER["PHP_AUTH_PW"]]; - return $authParts; - } - - private function checkAuth($public, $private) - { - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT aprv - FROM mqtta - WHERE apub = :apub - "); - $pdoQuery->execute([ - ":apub" => $public - ]); - $user=$pdoQuery->fetch(PDO::FETCH_ASSOC); - - if($this->verifyPassword($private, $this->_GeniSys->_helpers->oDecrypt($user["aprv"]))): - return True; - else: - return False; - endif; - } - - public function process() - { - if($this->invalidMethod): - return $this->_response(["Response"=>"FAILED","Message"=>"Invalid Method"], 405); - endif; - - $authHeaders=$this->getAuthHeaders(); - - if (!$authHeaders || !$authHeaders[0] || !$authHeaders[1]): - return $this->_response(["Response"=>"FAILED","Message"=>"No Authorisation Provided"], 401); - endif; - - if(!$this->checkAuth($authHeaders[0], $authHeaders[1])): - return $this->_response(["Response"=>"FAILED","Message"=>"Invalid Authorisation Provided"], 401); - endif; - - if ((int)method_exists($this, $this->_Endpoint) > 0): - return $this->_response($this->{$this->_Endpoint}($this->_Args)); - endif; - - return $this->_response(["Response"=>"FAILED","Message"=>"No Matching API Endpoint: ".$this->_Endpoint], 404); - } - - private function _requestStatus($code) { - - $status = [ - 200 => 'OK', - 401 => 'Not Authorized', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 500 => 'Internal Server Error', - ]; - - return ($status[$code])?$status[$code]:$status[500]; - } - - private function _response($data, $status = 200) - { - header("HTTP/1.1 " . $status . " " . $this->_requestStatus($status)); - return json_encode($data); - } - - protected function writeFile($file,$data) - { - $fps = fopen($file, 'w'); - fwrite($fps, print_r($data, TRUE)); - fclose($fps); - } - } diff --git a/Root/var/www/html/Hospital/Staff/API/Applications/User/index.php b/Root/var/www/html/Hospital/Staff/API/Applications/User/index.php deleted file mode 100644 index 91ec1e7..0000000 --- a/Root/var/www/html/Hospital/Staff/API/Applications/User/index.php +++ /dev/null @@ -1,75 +0,0 @@ - "HIS", - "SubPageID" => "API", - "LowPageID" => "Login" -]; - -include dirname(__FILE__) . '/../../../../../../Classes/Core/init.php'; -include dirname(__FILE__) . '/../../../../../../Classes/Core/GeniSys.php'; - -require_once 'Classes/Auth.php'; - -class Login extends Auth{ - - protected $_GeniSys; - - public function __construct($_GeniSys) - { - parent::__construct(); - $this->_GeniSys = $_GeniSys; - } - - public function Login() - { - if(!isset($_POST["apub"])): - return [ - "Response"=>"FAILED", - "Message"=>"Public key must be provided" - ]; - endif; - - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT users.id, - users.name, - mqtta.id as aid, - mqtta.mqttu, - mqtta.mqttp - FROM mqtta mqtta - INNER JOIN users users - ON users.id = mqtta.uid - WHERE mqtta.apub = :apub - "); - $pdoQuery->execute([ - ":apub" => $_POST["apub"] - ]); - $user=$pdoQuery->fetch(PDO::FETCH_ASSOC); - - if(!$user["id"]): - return [ - "Response"=>"FAILED", - "ResponseMessage"=>"Invalid user" - ]; - else: - return [ - "Response"=>"OK", - "Message"=>"Access granted", - "Data"=> [ - "AID" => $user["aid"], - "UID" => $user["id"], - "UN" => $user["name"] - ] - ]; - endif; - } -} - -try { - $Login = new Login($_GeniSys); - echo $Login->process(); -} catch (Exception $e) { - echo json_encode([ - 'error' => $e->getMessage() - ]); -} \ No newline at end of file diff --git a/Root/var/www/html/Hospital/Staff/API/Applications/User/pbkdf2.php b/Root/var/www/html/Hospital/Staff/API/Applications/User/pbkdf2.php deleted file mode 100644 index 2c03600..0000000 --- a/Root/var/www/html/Hospital/Staff/API/Applications/User/pbkdf2.php +++ /dev/null @@ -1,114 +0,0 @@ -_GeniSys = $_GeniSys; - $this->bcc = $this->getBlockchainConf(); - $this->web3 = $this->blockchainConnection(); - $this->contract = new Contract($this->web3->provider, $this->bcc["abi"]); - $this->icontract = new Contract($this->web3->provider, $this->bcc["iabi"]); - $allowed = $this->checkBlockchainPermissions(); + + if(isSet($_SESSION["GeniSysAI"]["Active"])): + $this->bcc = $this->getBlockchainConf(); + $this->web3 = $this->blockchainConnection(); + $this->contract = new Contract($this->web3->provider, $this->bcc["abi"]); + $this->icontract = new Contract($this->web3->provider, $this->bcc["iabi"]); + $this->pcontract = new Contract($this->web3->provider, $this->bcc["pabi"]); + $allowed = $this->checkBlockchainPermissions(); + endif; + $this->cb = $this->getContextBrokerConf(); + } + + public function getContextBrokerConf() + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM contextbroker + "); + $pdoQuery->execute(); + $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + return $response; + } + + private function createContextHeaders() + { + $basicAuth = $_SESSION["GeniSysAI"]["User"] . ":" . $this->_GeniSys->_helpers->oDecrypt($_SESSION["GeniSysAI"]["Pass"]); + $basicAuth = base64_encode($basicAuth); + + return [ + "Content-Type: application/json", + 'Authorization: Basic '. $basicAuth + ]; + } + + private function contextBrokerRequest($method, $endpoint, $headers, $json) + { + $path = $this->_GeniSys->_helpers->oDecrypt($this->_GeniSys->_confs["domainString"]) . "/" . $this->cb["url"] . "/" . $endpoint; + + if($method == "GET"): + $ch = curl_init(); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_URL, $path); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "POST"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + elseif($method == "PATCH"): + $ch = curl_init($path); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_POSTFIELDS, $json); + $response = curl_exec($ch); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $body = substr($response, $header_size); + curl_close($ch); + endif; + + return $body; } public function getBlockchainConf() @@ -28,12 +103,16 @@ public function getBlockchainConf() contracts.contract, contracts.abi, icontracts.contract as icontract, - icontracts.abi as iabi + icontracts.abi as iabi, + pcontracts.contract as pcontract, + pcontracts.abi as pabi FROM blockchain blockchain INNER JOIN contracts contracts ON contracts.id = blockchain.dc INNER JOIN contracts icontracts ON icontracts.id = blockchain.ic + INNER JOIN contracts pcontracts + ON pcontracts.id = blockchain.pc "); $pdoQuery->execute(); $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); @@ -113,17 +192,19 @@ private function getBlockchainBalance() return Utils::fromWei($nbalance, 'ether')[0]; } - private function storeBlockchainTransaction($action, $hash, $aid = 0) + private function storeBlockchainTransaction($action, $hash, $aid = 0, $uid = 0) { $pdoQuery = $this->_GeniSys->_secCon->prepare(" INSERT INTO transactions ( `uid`, + `tuid`, `action`, `hash`, `aid`, `time` ) VALUES ( :uid, + :tuid, :action, :hash, :aid, @@ -132,6 +213,7 @@ private function storeBlockchainTransaction($action, $hash, $aid = 0) "); $pdoQuery->execute([ ":uid" => $_SESSION["GeniSysAI"]["Uid"], + ":tuid" => $uid, ":action" => $action, ':hash' => $this->_GeniSys->_helpers->oEncrypt($hash), ":aid" => $aid, @@ -144,6 +226,114 @@ private function storeBlockchainTransaction($action, $hash, $aid = 0) return $txid; } + private function addAmqpUser($username, $key) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpu ( + `username`, + `pw` + ) VALUES ( + :username, + :pw + ) + "); + $query->execute([ + ':username' => $username, + ':pw' => $this->_GeniSys->_helpers->oEncrypt($key) + ]); + $amid = $this->_GeniSys->_secCon->lastInsertId(); + return $amid; + } + + private function addAmqpUserPerm($uid, $permission) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpp ( + `uid`, + `permission` + ) VALUES ( + :uid, + :permission + ) + "); + $query->execute([ + ':uid' => $uid, + ':permission' => $permission + ]); + } + + private function addAmqpUserVh($uid, $vhost) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvh ( + `uid`, + `vhost` + ) VALUES ( + :uid, + :vhost + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost + ]); + } + + private function addAmqpVhPerm($uid, $vhost, $rtype, $rname, $permission) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvhr ( + `uid`, + `vhost`, + `rtype`, + `rname`, + `permission` + ) VALUES ( + :uid, + :vhost, + :rtype, + :rname, + :permission + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost, + ':rtype' => $rtype, + ':rname' => $rname, + ':permission' => $permission + ]); + } + + private function addAmqpVhTopic($uid, $vhost, $rtype, $rname, $permission, $rkey) + { + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO amqpvhrt ( + `uid`, + `vhost`, + `rtype`, + `rname`, + `permission`, + `rkey` + ) VALUES ( + :uid, + :vhost, + :rtype, + :rname, + :permission, + :rkey + ) + "); + $query->execute([ + ':uid' => $uid, + ':vhost' => $vhost, + ':rtype' => $rtype, + ':rname' => $rname, + ':permission' => $permission, + ':rkey' => $rkey + ]); + } + private function storeUserHistory($action, $hashid, $uid, $aid = 0) { $pdoQuery = $this->_GeniSys->_secCon->prepare(" @@ -178,62 +368,133 @@ private function storeUserHistory($action, $hashid, $uid, $aid = 0) return $txid; } - public function getStaffs() + public function checkLocation($lid) { $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT users.id, - users.admin, - users.pic, - users.username, - mqtta.lid, - mqtta.status, - mqtta.mqttu, - mqtta.mqttp, - mqtta.id AS aid - FROM users users - INNER JOIN mqtta mqtta - ON users.id = mqtta.uid + SELECT id + FROM mqttl + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $lid + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($location["id"]): + return True; + else: + return False; + endif; + } + + public function getLocation($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqttl + WHERE id = :id + "); + $pdoQuery->execute([ + ":id" => $id + ]); + $location=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $location["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $location["pub"] . "?type=Location" . $attrs, $this->createContextHeaders(), []), true); + return $location; + } + + public function getApplication($id, $attrs = Null) + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM mqtta + WHERE id = :id ORDER BY id DESC "); + $pdoQuery->execute([ + ":id" => $id + ]); + $application=$pdoQuery->fetch(PDO::FETCH_ASSOC); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $application["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $application["apub"] . "?type=Application" . $attrs, $this->createContextHeaders(), []), true); + return $application; + } + + public function getStaffCategories() + { + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + SELECT category + FROM cbUserCats + ORDER BY category ASC + "); $pdoQuery->execute(); - $response=$pdoQuery->fetchAll(PDO::FETCH_ASSOC); + $categories=$pdoQuery->fetchAll(PDO::FETCH_ASSOC); $pdoQuery->closeCursor(); $pdoQuery = null; - return $response; + return $categories; } - public function getStaff($id) + public function getStaffs($limit = 0, $order = "id DESC") + { + $limiter = ""; + if($limit != 0): + $limiter = "&limit=" . $limit; + endif; + + $staff = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "?type=Staff".$limiter, $this->createContextHeaders(), []), true); + return $staff; + } + + public function getStaff($id, $attrs = Null) { $pdoQuery = $this->_GeniSys->_secCon->prepare(" - SELECT users.*, - mqtta.lid, - mqtta.status, - mqtta.mqttu, - mqtta.mqttp, - mqtta.apub, - mqtta.ip, - mqtta.mac, - mqtta.lt, - mqtta.lg, - mqtta.cpu, - mqtta.mem, - mqtta.hdd, - mqtta.tempr, - mqtta.id AS aid + SELECT users.* FROM users users - INNER JOIN mqtta mqtta - ON users.id = mqtta.uid WHERE users.id = :id "); $pdoQuery->execute([ ":id" => $id ]); - $response=$pdoQuery->fetch(PDO::FETCH_ASSOC); - return $response; + $staff=$pdoQuery->fetch(PDO::FETCH_ASSOC); + + if($attrs): + $attrs="&attrs=" . $attrs; + endif; + + $staff["context"] = json_decode($this->contextBrokerRequest("GET", $this->cb["entities_url"] . "/" . $staff["pub"] . "?type=Staff" . $attrs, $this->createContextHeaders(), []), true); + return $staff; } public function createStaff() { + if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location id is required" + ]; + endif; + + if(!$this->checkLocation(filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location does not exist" + ]; + endif; + if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): return [ "Response"=> "Failed", @@ -255,6 +516,27 @@ public function createStaff() ]; endif; + if(!filter_input(INPUT_POST, "streetAddress", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Location street address is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "addressLocality", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Location address locality is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "postalCode", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Location postal code is required" + ]; + endif; + $pdoQuery = $this->_GeniSys->_secCon->prepare(" SELECT id FROM users @@ -272,13 +554,6 @@ public function createStaff() ]; endif; - if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): - return [ - "Response"=> "Failed", - "Message" => "iotJumpWay location id is required" - ]; - endif; - $uPass = $this->_GeniSys->_helpers->password(); $passhash = $this->_GeniSys->_helpers->createPasswordHash($uPass); @@ -292,6 +567,10 @@ public function createStaff() $privKey = $this->_GeniSys->_helpers->generateKey(32); $privKeyHash = $this->_GeniSys->_helpers->createPasswordHash($privKey); + $amqppubKey = $this->_GeniSys->_helpers->generate_uuid(); + $amqpprvKey = $this->_GeniSys->_helpers->generateKey(32); + $amqpKeyHash = $this->_GeniSys->_helpers->createPasswordHash($amqpprvKey); + $unlocked = $this->unlockBlockchainAccount(); if($unlocked == "FAILED"): @@ -311,62 +590,32 @@ public function createStaff() endif; $lid = filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT); + $location = $this->getLocation($lid); + $admin = filter_input(INPUT_POST, "admin", FILTER_SANITIZE_NUMBER_INT) ? True : False; + $name = filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING); $htpasswd = new Htpasswd('/etc/nginx/security/htpasswd'); $htpasswd->addUser(filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING), $uPass, Htpasswd::ENCTYPE_APR_MD5); $pdoQuery = $this->_GeniSys->_secCon->prepare(" - INSERT INTO users ( - `admin`, - `bcaddress`, - `bcpw`, - `cancelled`, - `lid`, - `name`, - `email`, + INSERT INTO users ( `username`, `password`, - `nfc`, - `gpstime`, - `cz`, - `czt`, - `welcomed`, - `created` + `bcaddress`, + `bcpw` ) VALUES ( - :admin, - :bcaddress, - :bcpw, - :cancelled, - :lid, - :name, - :email, :username, :password, - :nfc, - :gpstime, - :cz, - :czt, - :welcomed, - :time + :bcaddress, + :bcpw ) "); $pdoQuery->execute([ - ":admin" => filter_input(INPUT_POST, "admin", FILTER_SANITIZE_NUMBER_INT) ? filter_input(INPUT_POST, "admin", FILTER_SANITIZE_NUMBER_INT) : 0, - ':bcaddress' => $this->_GeniSys->_helpers->oEncrypt($newBcUser), - ':bcpw' => $this->_GeniSys->_helpers->oEncrypt($bcPass), - ":cancelled" => 0, - ':lid' => $lid, - ":name" => filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING), - ":email" => filter_input(INPUT_POST, "email", FILTER_SANITIZE_STRING), ":username" => filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING), - ":password" => $this->_GeniSys->_helpers->oEncrypt($passhash), - ":nfc" => filter_input(INPUT_POST, "nfc", FILTER_SANITIZE_STRING) ? filter_input(INPUT_POST, "nfc", FILTER_SANITIZE_STRING) : "", - ":gpstime" => 0, - ":cz" => 0, - ":czt" => 0, - ":welcomed" => 0, - ":time" => time() + ":password" => $this->_GeniSys->_helpers->oEncrypt($passhash), + ":bcaddress" => $newBcUser, + ":bcpw" => $this->_GeniSys->_helpers->oEncrypt($bcPass) ]); $uid = $this->_GeniSys->_secCon->lastInsertId(); $pdoQuery->closeCursor(); @@ -374,313 +623,963 @@ public function createStaff() $query = $this->_GeniSys->_secCon->prepare(" INSERT INTO mqtta ( - `uid`, - `lid`, - `name`, - `mqttu`, - `mqttp`, - `apub`, - `aprv`, - `time` + `id` ) VALUES ( - :uid, - :lid, - :name, - :mqttu, - :mqttp, - :apub, - :aprv, - :time + :id ) "); $query->execute([ - ':uid' => $uid, - ':lid' => $lid, - ':name' => filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING), - ':mqttu' => $this->_GeniSys->_helpers->oEncrypt($mqttUser), - ':mqttp' => $this->_GeniSys->_helpers->oEncrypt($mqttPass), - ':apub' => $pubKey, - ':aprv' => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), - ':time' => time() + ':id' => 0 ]); $aid = $this->_GeniSys->_secCon->lastInsertId(); - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE users - SET aid = :aid - WHERE id = :id - "); - $query->execute(array( - ':aid' => $aid, - ':id' => $uid - )); - - $hash = ""; - $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("registerUser", $pubKey, $newBcUser, $admin, $uid, filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING), $lid, $aid, time(), $_SESSION["GeniSysAI"]["Uid"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash) { - if ($err !== null) { - $hash = "FAILED! " . $err; - return; - } - $hash = $resp; - }); + $data = [ + "id" => $pubKey, + "type" => "Application", + "category" => [ + "value" => [filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)] + ], + "name" => [ + "value" => $name + ], + "description" => [ + "value" => filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING) + ], + "lid" => [ + "value" => $lid, + "entity" => $location["context"]["Data"]["id"] + ], + "aid" => [ + "value" => $aid + ], + "admin" => [ + "value" => filter_input(INPUT_POST, "admin", FILTER_SANITIZE_NUMBER_INT) ? filter_input(INPUT_POST, "admin", FILTER_SANITIZE_NUMBER_INT) : 0 + ], + "patients" => [ + "value" => filter_input(INPUT_POST, "patients", FILTER_SANITIZE_NUMBER_INT) ? filter_input(INPUT_POST, "patients", FILTER_SANITIZE_NUMBER_INT) : 0 + ], + "cancelled" => [ + "value" => 0 + ], + "location" => [ + "type" => "geo:json", + "value" => [ + "type" => "Point", + "coordinates" => [0, 0] + ] + ], + "device" => [ + "name" => "", + "manufacturer" => "", + "model" => "", + "version" => "" + ], + "os" => [ + "name" => "", + "manufacturer" => "", + "version" => "" + ], + "protocols" => ["MQTT"], + "status" => [ + "value" => "OFFLINE", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "keys" => [ + "public" => $pubKey, + "private" => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "blockchain" => [ + "address" => $newBcUser, + "password" => $this->_GeniSys->_helpers->oEncrypt($bcPass) + ], + "mqtt" => [ + "username" => $this->_GeniSys->_helpers->oEncrypt($mqttUser), + "password" => $this->_GeniSys->_helpers->oEncrypt($mqttPass), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "coap" => [ + "username" => "", + "password" => "" + ], + "amqp" => [ + "username" => $this->_GeniSys->_helpers->oEncrypt($amqppubKey), + "password" => $this->_GeniSys->_helpers->oEncrypt($amqpprvKey), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "batteryLevel" => [ + "value" => 0.00 + ], + "cpuUsage" => [ + "value" => 0.00 + ], + "memoryUsage" => [ + "value" => 0.00 + ], + "hddUsage" => [ + "value" => 0.00 + ], + "temperature" => [ + "value" => 0.00 + ], + "ip" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt(""), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "mac" => [ + "value" => $this->_GeniSys->_helpers->oEncrypt(""), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "bluetooth" => [ + "address" => "", + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "ai" => [], + "sensors" => [], + "actuators" => [], + "dateCreated" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateFirstUsed" => [ + "type" => "DateTime", + "value" => "" + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; - $actionMsg = ""; - $balanceMessage = ""; + $response = json_decode($this->contextBrokerRequest("POST", $this->cb["entities_url"] . "?type=Application", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqtta + SET apub = :apub + WHERE id = :id + "); + $query->execute(array( + ':apub'=> $response["Entity"]["id"], + ':id'=> $aid + )); + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttu ( + `lid`, + `uid`, + `aid`, + `uname`, + `pw` + ) VALUES ( + :lid, + :uid, + :aid, + :uname, + :pw + ) + "); + $query->execute([ + ':lid' => $lid, + ':uid' => $uid, + ':aid' => $aid, + ':uname' => $mqttUser, + ':pw' => $mqttHash + ]); + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttua ( + `lid`, + `uid`, + `aid`, + `username`, + `topic`, + `rw` + ) VALUES ( + :lid, + :uid, + :aid, + :username, + :topic, + :rw + ) + "); + $query->execute(array( + ':lid' => $lid, + ':uid' => $uid, + ':aid' => $aid, + ':username' => $mqttUser, + ':topic' => $location["context"]["Data"]["id"] . "/Devices/#", + ':rw' => 4 + )); + + $query = $this->_GeniSys->_secCon->prepare(" + INSERT INTO mqttua ( + `lid`, + `uid`, + `aid`, + `username`, + `topic`, + `rw` + ) VALUES ( + :lid, + :uid, + :aid, + :username, + :topic, + :rw + ) + "); + $query->execute(array( + ':lid' => $lid, + ':uid' => $uid, + ':aid' => $aid, + ':username' => $mqttUser, + ':topic' => $location["context"]["Data"]["id"] . "/Applications/#", + ':rw' => 2 + )); + + $amid = $this->addAmqpUser($amqppubKey, $amqpKeyHash); + $this->addAmqpUserVh($amid, "iotJumpWay"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "exchange", "Core", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "exchange", "Core", "write"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Life", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Life", "write"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Statuses", "read"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Statuses", "write"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "read", "Life"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "write", "Life"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "read", "Statuses"); + $this->addAmqpVhTopic($amid, "iotJumpWay", "topic", "Core", "write", "Statuses"); + + if($admin): + $this->addAmqpUserPerm($amid, "administrator"); + $this->addAmqpUserPerm($amid, "managment"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Life", "configure"); + $this->addAmqpVhPerm($amid, "iotJumpWay", "queue", "Statuses", "configure"); + endif; - if($hash == "FAILED"): - $actionMsg = " HIAS Blockchain registerUser failed!"; - else: - $txid = $this->storeBlockchainTransaction("Register User", $hash); - $this->storeUserHistory("Register User", $txid, $uid); - endif; + $unlocked = $this->unlockBlockchainAccount(); - $this->icontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["icontract"]))->send("registerAuthorized", $newBcUser, ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { - if ($err !== null) { - $hash = "FAILED"; - $msg = $err; - return; - } - $hash = $resp; - }); - - if($hash == "FAILED"): - $actionMsg .= " HIAS Blockchain registerAuthorized failed! " . $msg; - else: - $txid = $this->storeBlockchainTransaction("iotJumpWay Register Authorized", $hash, $aid); - $this->storeUserHistory("Register Authorized", $txid, $uid, $aid); - $balance = $this->getBlockchainBalance(); - if($balanceMessage == ""): - $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; + if($unlocked == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Unlocking HIAS Blockhain Account Failed!" + ]; endif; - endif; - - $query = $this->_GeniSys->_secCon->prepare(" - INSERT INTO mqttu ( - `lid`, - `uid`, - `aid`, - `uname`, - `pw` - ) VALUES ( - :lid, - :uid, - :aid, - :uname, - :pw - ) - "); - $query->execute([ - ':lid' => $lid, - ':uid' => $uid, - ':aid' => $aid, - ':uname' => $mqttUser, - ':pw' => $mqttHash - ]); - $query = $this->_GeniSys->_secCon->prepare(" - INSERT INTO mqttua ( - `lid`, - `aid`, - `uid`, - `username`, - `topic`, - `rw` - ) VALUES ( - :lid, - :aid, - :uid, - :username, - :topic, - :rw - ) - "); - $query->execute(array( - ':lid' => $lid, - ':aid' => $aid, - ':uid' => $uid, - ':username' => $mqttUser, - ':topic' => $lid."/Devices/#", - ':rw' => 4 - )); + $hash = ""; + $msg = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("registerApplication", $pubKey, $newBcUser, $admin, $lid, $aid, $name, $_SESSION["GeniSysAI"]["Uid"], time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + $actionMsg = ""; + $balanceMessage = ""; + + if($hash == "FAILED"): + $actionMsg = " HIAS Blockchain registerApplication failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("Register Application", $hash, $aid, $uid); + $this->storeUserHistory("Register Application", $txid, $uid, $aid); + $balance = $this->getBlockchainBalance(); + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!\n"; + endif; - $query = $this->_GeniSys->_secCon->prepare(" - INSERT INTO mqttua ( - `lid`, - `aid`, - `uid`, - `username`, - `topic`, - `rw` - ) VALUES ( - :lid, - :aid, - :uid, - :username, - :topic, - :rw - ) - "); - $query->execute(array( - ':lid' => $lid, - ':aid' => $aid, - ':uid' => $uid, - ':username' => $mqttUser, - ':topic' => $lid."/Applications/#", - ':rw' => 4 - )); + $this->icontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["icontract"]))->send("registerAuthorized", $newBcUser, ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg .= " HIAS Blockchain registerAuthorized failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("iotJumpWay Register Authorized User Application", $hash, $aid, $uid); + $this->storeUserHistory("iotJumpWay Register Authorized User Application", $txid, $uid, $aid); + $balance = $this->getBlockchainBalance(); + if($balanceMessage == ""): + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!\n"; + endif; + endif; - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE mqttl - SET apps = apps + 1 - WHERE id = :id - "); - $query->execute(array( - ':id'=>$lid - )); + $data = [ + "id" => $pubKey, + "type" => "Staff", + "category" => [ + "value" => [filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)] + ], + "name" => [ + "value" => $name + ], + "username" => [ + "value" => filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING) + ], + "description" => [ + "value" => filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING) + ], + "email" => [ + "value" => filter_input(INPUT_POST, "email", FILTER_SANITIZE_STRING) + ], + "nfc" => [ + "value" => filter_input(INPUT_POST, "nfc", FILTER_SANITIZE_STRING) + ], + "picture" => [ + "value" => "default.png" + ], + "lid" => [ + "value" => $lid, + "entity" => $location["context"]["Data"]["id"] + ], + "zid" => [ + "value" => 0, + "entity" => "", + "timestamp" => "", + "welcomed" => "" + ], + "aid" => [ + "value" => $aid, + "entity" => $response["Entity"]["id"] + ], + "uid" => [ + "value" => $uid + ], + "permissions" => [ + "adminAccess" => filter_input(INPUT_POST, "admin", FILTER_SANITIZE_NUMBER_INT), + "patientsAccess" => filter_input(INPUT_POST, "patients", FILTER_SANITIZE_NUMBER_INT), + "cancelled" => 0 + ], + "address" => [ + "type" => "PostalAddress", + "value" => [ + "addressLocality" => filter_input(INPUT_POST, "addressLocality", FILTER_SANITIZE_STRING), + "postalCode" => filter_input(INPUT_POST, "postalCode", FILTER_SANITIZE_STRING), + "streetAddress" => filter_input(INPUT_POST, "streetAddress", FILTER_SANITIZE_STRING) + ] + ], + "keys" => [ + "public" => $pubKey, + "private" => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), + "nfc" => filter_input(INPUT_POST, "nfc", FILTER_SANITIZE_STRING), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "blockchain" => [ + "address" => $newBcUser, + "password" => $this->_GeniSys->_helpers->oEncrypt($bcPass) + ], + "mqtt" => [ + "username" => $this->_GeniSys->_helpers->oEncrypt($mqttUser), + "password" => $this->_GeniSys->_helpers->oEncrypt($mqttPass), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "coap" => [ + "username" => "", + "password" => "" + ], + "amqp" => [ + "username" => $this->_GeniSys->_helpers->oEncrypt($amqppubKey), + "password" => $this->_GeniSys->_helpers->oEncrypt($amqpprvKey), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "status" => [ + "value" => "OFFLINE" + ], + "dateCreated" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateFirstUsed" => [ + "type" => "DateTime", + "value" => "" + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; - return [ - "Response"=> "OK", - "Message" => "Staff & application created!" . $actionMsg . $balanceMessage, - "UID" => $uid, - "Uname" => filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING), - "Upass" => $uPass, - "AppID" => $pubKey, - "AppKey" => $privKey, - "BCU" => $newBcUser, - "BCP" => $bcPass, - "MU" => $mqttUser, - "MP" => $mqttPass - ]; + $response = json_decode($this->contextBrokerRequest("POST", $this->cb["entities_url"] . "?type=Staff", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE users + SET pub = :pub, + aid = :aid + WHERE id = :id + "); + $query->execute(array( + ':pub' => $response["Entity"]["id"], + ':aid' => $aid, + ':id' => $uid + )); + + $hash = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("registerUser", $pubKey, $newBcUser, $admin, $uid, filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING), $lid, $aid, time(), $_SESSION["GeniSysAI"]["Uid"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + $actionMsg = ""; + $balanceMessage = ""; + + if($hash == "FAILED"): + $actionMsg = " HIAS Blockchain registerUser failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("HIAS Register User", $hash, $aid, $uid); + $this->storeUserHistory("HIAS Register User", $txid, $uid, $aid); + endif; + + $this->icontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["icontract"]))->send("registerAuthorized", $newBcUser, ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg .= " HIAS Blockchain registerAuthorized failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("iotJumpWay Register Authorized", $hash, $aid, $uid); + $this->storeUserHistory("iotJumpWay Register Authorized", $txid, $uid, $aid); + $balance = $this->getBlockchainBalance(); + if($balanceMessage == ""): + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!"; + endif; + endif; + + if(filter_input(INPUT_POST, "patients", FILTER_SANITIZE_STRING)): + + $this->pcontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["pcontract"]))->send("registerUser", $newBcUser, ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg .= " HIAS Blockchain patients registerUser failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("Patients Register User", $hash, $aid, $uid); + $this->storeUserHistory("Patients Register User", $txid, $uid, $aid); + $balance = $this->getBlockchainBalance(); + if($balanceMessage == ""): + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!\n"; + endif; + endif; + + endif; + + return [ + "Response"=> "OK", + "Message" => "Staff & application created!" . $actionMsg . $balanceMessage, + "UID" => $uid, + "Uname" => filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING), + "Upass" => $uPass, + "AppID" => $pubKey, + "AppKey" => $privKey, + "BCU" => $newBcUser, + "BCP" => $bcPass, + "MU" => $mqttUser, + "MP" => $mqttPass, + "AU" => $amqppubKey, + "AP" => $amqpprvKey + ]; + else: + return [ + "Response"=> "FAILED", + "Message" => "User creation failed!" + ]; + endif; + else: + return [ + "Response"=> "FAILED", + "Message" => "User creation failed!" + ]; + endif; } public function updateStaff() { - $response = ''; + if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location id is required" + ]; + endif; + + if(!$this->checkLocation(filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT))): + return [ + "Response"=> "Failed", + "Message" => "iotJumpWay location does not exist" + ]; + endif; - if(!filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT)): + if(!filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING)): return [ "Response"=> "Failed", - "Message" => "Staff ID is required" + "Message" => "Staff name is required" ]; endif; + if(!filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING)): return [ "Response"=> "Failed", "Message" => "Staff username is required" ]; endif; - if(!filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT)): + + if(!filter_input(INPUT_POST, "email", FILTER_VALIDATE_EMAIL)): + return [ + "Response"=> "Failed", + "Message" => "Staff email is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "streetAddress", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Location street address is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "addressLocality", FILTER_SANITIZE_STRING)): + return [ + "Response"=> "Failed", + "Message" => "Location address locality is required" + ]; + endif; + + if(!filter_input(INPUT_POST, "postalCode", FILTER_SANITIZE_STRING)): return [ "Response"=> "Failed", - "Message" => "Staff iotJumpWay application location id is required" + "Message" => "Location postal code is required" ]; endif; - if(!filter_input(INPUT_POST, "aid", FILTER_SANITIZE_NUMBER_INT)): + + $SId = filter_input(INPUT_GET, 'staff', FILTER_SANITIZE_NUMBER_INT); + $Staffer = $this->getStaff($SId); + + if($Staffer["context"]["Data"]["permissions"]["cancelled"]): return [ "Response"=> "Failed", - "Message" => "Staff iotJumpWay application id is required" + "Message" => "This user is cancelled, to allow access again you must create a new user." ]; endif; - $id = filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT); $lid = filter_input(INPUT_POST, "lid", FILTER_SANITIZE_NUMBER_INT); - $aid = filter_input(INPUT_POST, "aid", FILTER_SANITIZE_NUMBER_INT); + $aid = $Staffer["context"]["Data"]["aid"]["value"]; + $name = filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING); $allowed = filter_input(INPUT_POST, "cancelled", FILTER_SANITIZE_STRING) ? False : True; $admin = filter_input(INPUT_POST, "admin", FILTER_SANITIZE_STRING) ? True : False; + $pallowed = filter_input(INPUT_POST, "patients", FILTER_SANITIZE_STRING) ? True : False; + + $data = [ + "category" => [ + "value" => [filter_input(INPUT_POST, "category", FILTER_SANITIZE_STRING)] + ], + "name" => [ + "value" => $name + ], + "username" => [ + "value" => filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING) + ], + "description" => [ + "value" => filter_input(INPUT_POST, "description", FILTER_SANITIZE_STRING) + ], + "email" => [ + "value" => filter_input(INPUT_POST, "email", FILTER_SANITIZE_STRING) + ], + "nfc" => [ + "value" => filter_input(INPUT_POST, "nfc", FILTER_SANITIZE_STRING) + ], + "picture" => [ + "value" => $Staffer["context"]["Data"]["picture"]["value"] + ], + "lid" => [ + "value" => $lid, + "entity" => $Staffer["context"]["Data"]["lid"]["entity"] + ], + "zid" => [ + "value" => 0, + "entity" => "", + "timestamp" => "", + "welcomed" => "" + ], + "permissions" => [ + "adminAccess" => filter_input(INPUT_POST, "admin", FILTER_SANITIZE_NUMBER_INT) ? filter_input(INPUT_POST, "admin", FILTER_SANITIZE_NUMBER_INT) : 0, + "patientsAccess" => filter_input(INPUT_POST, "patients", FILTER_SANITIZE_NUMBER_INT) ? filter_input(INPUT_POST, "patients", FILTER_SANITIZE_NUMBER_INT) : 0, + "cancelled" => filter_input(INPUT_POST, "cancelled", FILTER_SANITIZE_NUMBER_INT) ? filter_input(INPUT_POST, "cancelled", FILTER_SANITIZE_NUMBER_INT) : 0 + ], + "address" => [ + "type" => "PostalAddress", + "value" => [ + "addressLocality" => filter_input(INPUT_POST, "addressLocality", FILTER_SANITIZE_STRING), + "postalCode" => filter_input(INPUT_POST, "postalCode", FILTER_SANITIZE_STRING), + "streetAddress" => filter_input(INPUT_POST, "streetAddress", FILTER_SANITIZE_STRING) + ] + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $Staffer["context"]["Data"]["id"] . "/attrs?type=Staff", $this->createContextHeaders(), json_encode($data)), true); + if($response["Response"]=="OK"): + + $data = [ + "lid" => [ + "value" => $lid, + "entity" => $Staffer["context"]["Data"]["lid"]["entity"] + ], + "admin" => [ + "value" => filter_input(INPUT_POST, "admin", FILTER_SANITIZE_NUMBER_INT) ? filter_input(INPUT_POST, "admin", FILTER_SANITIZE_NUMBER_INT) : 0 + ], + "patients" => [ + "value" => filter_input(INPUT_POST, "patients", FILTER_SANITIZE_NUMBER_INT) ? filter_input(INPUT_POST, "patients", FILTER_SANITIZE_NUMBER_INT) : 0 + ], + "cancelled" => [ + "value" => filter_input(INPUT_POST, "cancelled", FILTER_SANITIZE_NUMBER_INT) ? filter_input(INPUT_POST, "cancelled", FILTER_SANITIZE_NUMBER_INT) : 0 + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $Staffer["context"]["Data"]["aid"]["entity"] . "/attrs?type=Application", $this->createContextHeaders(), json_encode($data)), true); + + $pdoQuery = $this->_GeniSys->_secCon->prepare(" + UPDATE users + SET username = :username + WHERE id = :id + "); + $pdoQuery->execute([ + ":username" => filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING), + ":id" => $SId + ]); + + if($Staffer["context"]["Data"]["lid"]["value"] != $lid): + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttu + SET lid = :lid + WHERE aid = :aid + "); + $query->execute([ + ':lid' => $lid, + ':aid' => $aid + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttua + SET lid = :lid + WHERE aid = :aid + "); + $query->execute([ + ':lid' => $lid, + ':aid' => $aid + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttua + SET topic = :topicN + WHERE aid = :aid + & topic = :topic + "); + $query->execute([ + ':topicN' => $Staffer["context"]["Data"]["lid"]["entity"] . "/Devices/#", + ':aid' => $aid, + ':topic' => $location["context"]["Data"]["id"] . "/Devices/#" + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttua + SET topic = :topicN + WHERE aid = :aid + & topic = :topic + "); + $query->execute([ + ':topicN' => $Staffer["context"]["Data"]["lid"]["entity"] . "/Applications/#", + ':aid' => $aid, + ':topic' => $location["context"]["Data"]["id"] . "/Applications/#" + ]); + $pdoQuery->closeCursor(); + $pdoQuery = null; + endif; - $pdoQuery = $this->_GeniSys->_secCon->prepare(" - UPDATE users - SET username = :username, - name = :name, - admin = :admin, - cancelled = :cancelled, - nfc = :nfc, - lid = :lid, - aid = :aid - WHERE id = :id - "); - $pdoQuery->execute([ - ":username" => filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING), - ":name" => filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING), - ":admin" => filter_input(INPUT_POST, "admin", FILTER_SANITIZE_STRING) ? filter_input(INPUT_POST, "admin", FILTER_SANITIZE_STRING) : 0, - ":cancelled" => filter_input(INPUT_POST, "cancelled", FILTER_SANITIZE_STRING) ? filter_input(INPUT_POST, "cancelled", FILTER_SANITIZE_STRING) : 0, - ":nfc" => filter_input(INPUT_POST, "nfc", FILTER_SANITIZE_STRING) ? filter_input(INPUT_POST, "nfc", FILTER_SANITIZE_STRING) : "", - ":lid" => $lid, - ":aid" => $aid, - ":id" => $id - ]); + $unlocked = $this->unlockBlockchainAccount(); - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE mqtta - SET lid = :lid - WHERE id = :id - "); - $query->execute([ - ':lid' => $lid, - ':id' => $aid - ]); + if($unlocked == "FAILED"): + return [ + "Response"=> "Failed", + "Message" => "Unlocking HIAS Blockhain Account Failed!" + ]; + endif; - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE mqttu - SET lid = :lid - WHERE aid = :aid - "); - $query->execute([ - ':lid' => $lid, - ':aid' => $aid - ]); - $pdoQuery->closeCursor(); - $pdoQuery = null; + $hash = ""; + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("updateUser", $Staffer["context"]["Data"]["aid"]["entity"], "User", $allowed, $admin, $SId, $name, $lid, $aid, time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash) { + if ($err !== null) { + $hash = "FAILED! " . $err; + return; + } + $hash = $resp; + }); + + $actionMsg = ""; + $balanceMessage = ""; + + if($hash == "FAILED"): + $actionMsg = " HIAS Blockchain updateUser failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("Update User", $hash, $aid, $SId); + $this->storeUserHistory("Update User", $txid, $SId, $aid); + endif; - $unlocked = $this->unlockBlockchainAccount(); + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("updateApplication", $Staffer["context"]["Data"]["aid"]["entity"], "Application", $allowed, $admin, $lid, $name, $Staffer["context"]["Data"]["status"]["value"], time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash) { + if ($err !== null) { + $hash = "FAILED! " . $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg .= " HIAS Blockchain updateApplication failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("Update User Application", $hash, $aid, $SId); + $this->storeUserHistory("Update User Application", $txid, $SId, $aid); + $balance = $this->getBlockchainBalance(); + $balanceMessage = " You were rewarded for this action!

Your Balance Is Now: " . $balance . " HIAS Ether!\n"; + endif; - if($unlocked == "FAILED"): - return [ - "Response"=> "Failed", - "Message" => "Unlocking HIAS Blockhain Account Failed!" - ]; - endif; + if($Staffer["context"]["Data"]["permissions"]["patientsAccess"] && !filter_input(INPUT_POST, "patients", FILTER_SANITIZE_STRING)): + + $this->pcontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["pcontract"]))->send("deregisterUser", $Staffer["context"]["Data"]["blockchain"]["address"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg .= " HIAS Blockchain patients deregisterUser failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("Patients Deregister User", $hash, $aid, $SId); + $this->storeUserHistory("Patients Deregister User", $txid, $SId, $aid); + $balance = $this->getBlockchainBalance(); + if($balanceMessage == ""): + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!\n"; + endif; + endif; - $hash = ""; - $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("updateUser", filter_input(INPUT_POST, "identifier", FILTER_SANITIZE_STRING), "User", $allowed, $admin, $id, filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING), $lid, $aid, time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash) { - if ($err !== null) { - $hash = "FAILED! " . $err; - return; - } - $hash = $resp; - }); + endif; - $actionMsg = ""; - $balanceMessage = ""; + if(!$Staffer["context"]["Data"]["permissions"]["patientsAccess"] && filter_input(INPUT_POST, "patients", FILTER_SANITIZE_STRING)): + + $this->pcontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["pcontract"]))->send("registerUser", $Staffer["context"]["Data"]["blockchain"]["address"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg .= " HIAS Blockchain patients registerUser failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("Patients Register User", $hash, $aid, $SId); + $this->storeUserHistory("Patients Register User", $txid, $SId, $aid); + $balance = $this->getBlockchainBalance(); + if($balanceMessage == ""): + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!\n"; + endif; + endif; - if($hash == "FAILED"): - $actionMsg = " HIAS Blockchain updateUser failed!"; - else: - $txid = $this->storeBlockchainTransaction("Update User", $hash); - $this->storeUserHistory("Update User", $txid, $id); - endif; + endif; - $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("updateApplication", filter_input(INPUT_POST, "identifier", FILTER_SANITIZE_STRING), "Application", $allowed, $admin, $lid, filter_input(INPUT_POST, "name", FILTER_SANITIZE_STRING), filter_input(INPUT_POST, "status", FILTER_SANITIZE_STRING), time(), ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash) { - if ($err !== null) { - $hash = "FAILED! " . $err; - return; - } - $hash = $resp; - }); + if(!$Staffer["context"]["Data"]["permissions"]["cancelled"] && filter_input(INPUT_POST, "cancelled", FILTER_SANITIZE_STRING)): + + $query = $this->_GeniSys->_secCon->prepare(" + DELETE FROM mqttu + WHERE aid = :aid + "); + $query->execute([ + ':aid' => $aid + ]); + + $query = $this->_GeniSys->_secCon->prepare(" + DELETE FROM mqttua + WHERE aid = :aid + "); + $query->execute([ + ':aid' => $aid + ]); + + $query = $this->_GeniSys->_secCon->prepare(" + SELECT * + FROM amqpu + WHERE username = :username + "); + $query->execute([ + ':username' => $this->_GeniSys->_helpers->oDecrypt($Staffer["context"]["Data"]["amqp"]["username"]) + ]); + $amqp=$query->fetch(PDO::FETCH_ASSOC); + + $query = $this->_GeniSys->_secCon->prepare(" + DELETE FROM amqpu + WHERE username = :username + "); + $query->execute([ + ':username' => $this->_GeniSys->_helpers->oDecrypt($Staffer["context"]["Data"]["amqp"]["username"]) + ]); + + $query = $this->_GeniSys->_secCon->prepare(" + DELETE FROM amqpp + WHERE uid = :uid + "); + $query->execute([ + ':uid' => $amqp["id"] + ]); + + $query = $this->_GeniSys->_secCon->prepare(" + DELETE FROM amqpvh + WHERE uid = :uid + "); + $query->execute([ + ':uid' => $amqp["id"] + ]); + + $query = $this->_GeniSys->_secCon->prepare(" + DELETE FROM amqpvhr + WHERE uid = :uid + "); + $query->execute([ + ':uid' => $amqp["id"] + ]); + + $query = $this->_GeniSys->_secCon->prepare(" + DELETE FROM amqpvhrt + WHERE uid = :uid + "); + $query->execute([ + ':uid' => $amqp["id"] + ]); + + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("deregsiter", "User", $Staffer["context"]["Data"]["id"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash) { + if ($err !== null) { + $hash = "FAILED! " . $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg .= " HIAS Blockchain deregsiter user failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("Deregister User", $hash, $aid, $SId); + $this->storeUserHistory("Deregister User", $txid, $SId, $aid); + $balance = $this->getBlockchainBalance(); + if($balanceMessage == ""): + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!\n"; + endif; + endif; + + $this->contract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["contract"]))->send("deregsiter", "Application", $Staffer["context"]["Data"]["id"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash) { + if ($err !== null) { + $hash = "FAILED! " . $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg .= " HIAS Blockchain deregsiter user application failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("Deregister User Application", $hash, $aid, $SId); + $this->storeUserHistory("Deregister User Application", $txid, $SId, $aid); + $balance = $this->getBlockchainBalance(); + if($balanceMessage == ""): + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!\n"; + endif; + endif; + + $this->icontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["icontract"]))->send("deregisterAuthorized", $Staffer["context"]["Data"]["blockchain"]["address"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg .= " HIAS Blockchain deregisterAuthorized failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("iotJumpWay Deregister Authorized", $hash, $aid, $SId); + $this->storeUserHistory("iotJumpWay Deregister Authorized", $txid, $SId, $aid); + $balance = $this->getBlockchainBalance(); + if($balanceMessage == ""): + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!\n"; + endif; + endif; + + $this->pcontract->at($this->_GeniSys->_helpers->oDecrypt($this->bcc["pcontract"]))->send("deregisterUser", $Staffer["context"]["Data"]["blockchain"]["address"], ["from" => $_SESSION["GeniSysAI"]["BC"]["BCUser"]], function ($err, $resp) use (&$hash, &$msg) { + if ($err !== null) { + $hash = "FAILED"; + $msg = $err; + return; + } + $hash = $resp; + }); + + if($hash == "FAILED"): + $actionMsg .= " HIAS Blockchain patients deregisterAuthorized failed!\n"; + else: + $txid = $this->storeBlockchainTransaction("Patients Deregister User", $hash, $aid, $SId); + $this->storeUserHistory("Patients Deregister Authorized", $txid, $SId, $aid); + $balance = $this->getBlockchainBalance(); + if($balanceMessage == ""): + $balanceMessage = " You were rewarded for this action! Your balance is now: " . $balance . " HIAS Ether!\n"; + endif; + endif; + + endif; - if($hash == "FAILED"): - $actionMsg = " HIAS Blockchain updateApplication failed!"; + return [ + "Response"=> "OK", + "Message" => "Staff updated!" . $actionMsg . $balanceMessage + ]; else: - $txid = $this->storeBlockchainTransaction("Update User Application", $hash); - $this->storeUserHistory("Update User Application", $txid, $id, $aid); - $balance = $this->getBlockchainBalance(); - $balanceMessage = " You were rewarded for this action!

Your Balance Is Now: " . $balance . " HIAS Ether!"; + return [ + "Response"=> "FAILED", + "Message" => "Staff update failed!" + ]; endif; - - return [ - "Response"=> "OK", - "Message" => "Staff updated!" . $actionMsg . $balanceMessage - ]; } public function resetPassword() @@ -688,9 +1587,11 @@ public function resetPassword() $pass = $this->_GeniSys->_helpers->password(); $passhash=$this->_GeniSys->_helpers->createPasswordHash($pass); + $SId = filter_input(INPUT_GET, 'staff', FILTER_SANITIZE_NUMBER_INT); + $Staffer = $Staff->getStaff($SId); + $htpasswd = new Htpasswd('/etc/nginx/security/htpasswd'); - //$htpasswd->addUser(filter_input(INPUT_POST, "username", FILTER_SANITIZE_STRING), $pass, Htpasswd::ENCTYPE_APR_MD5); - $htpasswd->updateUser(filter_input(INPUT_POST, "user", FILTER_SANITIZE_STRING), $pass, Htpasswd::ENCTYPE_APR_MD5); + $htpasswd->updateUser($Staffer["context"]["Data"]["username"]["value"], $pass, Htpasswd::ENCTYPE_APR_MD5); $query = $this->_GeniSys->_secCon->prepare(" UPDATE users @@ -699,16 +1600,15 @@ public function resetPassword() "); $query->execute(array( ':password' => $this->_GeniSys->_helpers->oEncrypt($passhash), - ':id' => filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT) + ':id' => $SId )); - $this->storeUserHistory("Reset User Password", 0, filter_input(INPUT_POST, "id", FILTER_SANITIZE_STRING)); + $this->storeUserHistory("Reset Staff Password", 0, $SId); return [ "Response" => "OK", "pw" => $pass ]; - } public function resetMqtt() @@ -716,34 +1616,56 @@ public function resetMqtt() $mqttPass = $this->_GeniSys->_helpers->password(); $mqttHash = create_hash($mqttPass); - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE mqtta - SET mqttp = :mqttp - WHERE id = :id - "); - $query->execute(array( - ':mqttp' => $this->_GeniSys->_helpers->oEncrypt($mqttPass), - ':id' => filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT) - )); - - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE mqttu - SET pw = :pw - WHERE aid = :aid - "); - $query->execute(array( - ':pw' => $mqttHash, - ':aid' => filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT) - )); - - $this->storeUserHistory("Reset User MQTT Password", 0, filter_input(INPUT_POST, "uid", FILTER_SANITIZE_STRING)); - - return [ - "Response"=> "OK", - "Message" => "MQTT password reset!", - "P" => $mqttPass + $SId = filter_input(INPUT_GET, 'staff', FILTER_SANITIZE_NUMBER_INT); + $Staffer = $this->getStaff($SId); + + $data = [ + "mqtt" => [ + "username" => $Staffer["context"]["Data"]["mqtt"]["username"], + "password" => $this->_GeniSys->_helpers->oEncrypt($mqttPass), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] ]; + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $Staffer["context"]["Data"]["aid"]["entity"] . "/attrs?type=Application", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $Staffer["context"]["Data"]["id"] . "/attrs?type=Staff", $this->createContextHeaders(), json_encode($data)), true); + if($response["Response"]=="OK"): + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE mqttu + SET pw = :pw + WHERE aid = :aid + "); + $query->execute(array( + ':pw' => $mqttHash, + ':aid' => $Staffer["context"]["Data"]["aid"]["value"] + )); + + $this->storeUserHistory("Reset User MQTT Password", $txid, $SId, $Staffer["context"]["Data"]["aid"]["value"]); + + return [ + "Response"=> "OK", + "Message" => "MQTT password reset!", + "P" => $mqttPass + ]; + else: + return [ + "Response"=> "FAILED", + "Message" => "MQTT password reset failed!" + ]; + endif; + else: + return [ + "Response"=> "FAILED", + "Message" => "MQTT password reset failed!" + ]; + endif; + } public function resetAppKey() @@ -751,50 +1673,103 @@ public function resetAppKey() $privKey = $this->_GeniSys->_helpers->generateKey(32); $privKeyHash = $this->_GeniSys->_helpers->createPasswordHash($privKey); - $query = $this->_GeniSys->_secCon->prepare(" - UPDATE mqtta - SET aprv = :aprv - WHERE id = :id - "); - $query->execute(array( - ':aprv' => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), - ':id' => filter_input(INPUT_POST, "id", FILTER_SANITIZE_NUMBER_INT) - )); + $SId = filter_input(INPUT_GET, 'staff', FILTER_SANITIZE_NUMBER_INT); + $Staffer = $this->getStaff($SId); + + $data = [ + "keys" => [ + "public" => $Staffer["context"]["Data"]["keys"]["public"], + "private" => $this->_GeniSys->_helpers->oEncrypt($privKeyHash), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; - $this->storeUserHistory("Reset User API Key", 0, filter_input(INPUT_POST, "uid", FILTER_SANITIZE_STRING)); + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $Staffer["context"]["Data"]["aid"]["entity"] . "/attrs?type=Application", $this->createContextHeaders(), json_encode($data)), true); - return [ - "Response"=> "OK", - "Message" => "API Key password reset!", - "P" => $privKey - ]; + if($response["Response"]=="OK"): + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $Staffer["context"]["Data"]["id"] . "/attrs?type=Staff", $this->createContextHeaders(), json_encode($data)), true); + if($response["Response"]=="OK"): - } + $this->storeUserHistory("Reset Private API Key", 0, $SId, $Staffer["context"]["Data"]["aid"]["value"]); - public function getMapMarkers($application) - { - if(!$application["lt"]): - $lat = $this->lat; - $lng = $this->lng; + return [ + "Response"=> "OK", + "Message" => "Reset Private API Key!", + "P" => $privKey + ]; + else: + return [ + "Response"=> "FAILED", + "Message" => "Reset Private API Key failed!" + ]; + endif; else: - $lat = $application["lt"]; - $lng = $application["lg"]; + return [ + "Response"=> "FAILED", + "Message" => "Reset Private API Key failed!" + ]; endif; - - return [$lat, $lng]; } - public function getStatusShow($status) + public function resetAppAmqpKey() { - if($status=="ONLINE"): - $on = " "; - $off = " hide "; + $SId = filter_input(INPUT_GET, 'staff', FILTER_SANITIZE_NUMBER_INT); + $Staffer = $this->getStaff($SId); + + $amqpPass = $this->_GeniSys->_helpers->password(); + $amqpHash = $this->_GeniSys->_helpers->createPasswordHash($amqpPass); + + $data = [ + "amqp" => [ + "username" => $Staffer["context"]["Data"]["amqp"]["username"], + "password" => $this->_GeniSys->_helpers->oEncrypt($amqpPass), + "timestamp" => date('Y-m-d\TH:i:s.Z\Z', time()) + ], + "dateModified" => [ + "type" => "DateTime", + "value" => date('Y-m-d\TH:i:s.Z\Z', time()) + ] + ]; + + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $Staffer["context"]["Data"]["aid"]["entity"] . "/attrs?type=Application", $this->createContextHeaders(), json_encode($data)), true); + + if($response["Response"]=="OK"): + $response = json_decode($this->contextBrokerRequest("PATCH", $this->cb["entities_url"] . "/" . $Staffer["context"]["Data"]["id"] . "/attrs?type=Staff", $this->createContextHeaders(), json_encode($data)), true); + if($response["Response"]=="OK"): + + $query = $this->_GeniSys->_secCon->prepare(" + UPDATE amqpu + SET pw = :pw + WHERE username = :username + "); + $query->execute(array( + ':pw' => $this->_GeniSys->_helpers->oEncrypt($amqpHash), + ':username' => $this->_GeniSys->_helpers->oDecrypt($Staffer["context"]["Data"]["amqp"]["username"]) + )); + + $this->storeUserHistory("Reset User Application AMQP Password", 0, $SId, $Staffer["context"]["Data"]["aid"]["value"]); + + return [ + "Response"=> "OK", + "Message" => "AMQP password reset!", + "P" => $amqpPass + ]; + else: + return [ + "Response"=> "FAILED", + "Message" => "AMQP password reset failed!" + ]; + endif; else: - $on = " hide "; - $off = " "; + return [ + "Response"=> "FAILED", + "Message" => "AMQP password reset failed!" + ]; endif; - - return [$on, $off]; } public function retrieveTransactions($user, $limit = 0, $order = "") @@ -813,11 +1788,13 @@ public function retrieveTransactions($user, $limit = 0, $order = "") SELECT * FROM transactions WHERE uid = :id + || tuid = :tuid $orderer $limiter "); $pdoQuery->execute([ - ":id" => $user + ":id" => $user, + ":tuid" => $user ]); $response=$pdoQuery->fetchAll(PDO::FETCH_ASSOC); return $response; @@ -839,11 +1816,13 @@ public function retrieveHistory($user, $limit = 0, $order = "") SELECT * FROM history WHERE uid = :id + || tuid = :tuid $orderer $limiter "); $pdoQuery->execute([ - ":id" => $user + ":id" => $user, + ":tuid" => $user ]); $response=$pdoQuery->fetchAll(PDO::FETCH_ASSOC); return $response; @@ -851,7 +1830,7 @@ public function retrieveHistory($user, $limit = 0, $order = "") public function retrieveStatuses($application, $limit = 0, $order = -1) { - $mngConn = new MongoDB\Driver\Manager('mongodb://'.$this->_GeniSys->_mdbusername.':'.$this->_GeniSys->_mdbpassword.'@localhost/'.$this->_GeniSys->_mdbname.''); + $mngConn = new MongoDB\Driver\Manager("mongodb://localhost:27017/".$this->_GeniSys->_mdbname.'', ["username" => $this->_GeniSys->_mdbusername, "password" => $this->_GeniSys->_mdbpassword]); $query = new MongoDB\Driver\Query(['Application' => strval($application)], ['limit' => $limit, 'sort' => ['Time' => $order]]); $rows = $mngConn->executeQuery($this->_GeniSys->_mdbname.".Statuses", $query); @@ -895,4 +1874,8 @@ public function retrieveStatuses($application, $limit = 0, $order = -1) if(filter_input(INPUT_POST, "reset_appkey_staff", FILTER_SANITIZE_NUMBER_INT)): die(json_encode($Staff->resetAppKey())); + endif; + + if(filter_input(INPUT_POST, "reset_user_amqp", FILTER_SANITIZE_NUMBER_INT)): + die(json_encode($Staff->resetAppAmqpKey())); endif; \ No newline at end of file diff --git a/Root/var/www/html/Hospital/Staff/Create.php b/Root/var/www/html/Hospital/Staff/Create.php index 424daf4..ce3ffb5 100644 --- a/Root/var/www/html/Hospital/Staff/Create.php +++ b/Root/var/www/html/Hospital/Staff/Create.php @@ -37,7 +37,7 @@ - + @@ -100,37 +100,84 @@
- - - Name of staff member + + + Name of staff
- - - Username of staff member + + + Staff description +
+
+ + + Username of staff +
+
+ + + Staff category
Email of staff member
+
+ + + iotJumpWay Location street address +
+
+ + + iotJumpWay Location address locality +
+
+ + + iotJumpWay Location post code +
UID of staff member's NFC card/fob/implant
+
+ + +
+
+
- Location of staff member -
-
- - + Location of staff
-
-
- + Is staff member an admin?
- - Is staff member has patients access? + + Does staff member has patients access?
diff --git a/Root/var/www/html/Hospital/Staff/Staff.php b/Root/var/www/html/Hospital/Staff/Staff.php index 0463043..abefda9 100644 --- a/Root/var/www/html/Hospital/Staff/Staff.php +++ b/Root/var/www/html/Hospital/Staff/Staff.php @@ -14,15 +14,15 @@ $Locations = $iotJumpWay->getLocations(0, "id ASC"); $Zones = $iotJumpWay->getZones(0, "id ASC"); -$MDevices = $iotJumpWay->getMDevices(0, "id ASC"); $Applications = $iotJumpWay->getApplications(0, "id ASC"); $SId = filter_input(INPUT_GET, 'staff', FILTER_SANITIZE_NUMBER_INT); $Staffer = $Staff->getStaff($SId); +$Application = $iotJumpWay->getApplication($Staffer["context"]["Data"]["aid"]["value"]); -list($lat, $lng) = $Staff->getMapMarkers($Staffer); -list($on, $off) = $Staff->getStatusShow($Staffer["status"]); +list($on, $off) = $iotJumpWay->getStatusShow($Application["context"]["Data"]["status"]["value"]); +$cancelled = $Staffer["context"]["Data"]["permissions"]["cancelled"] ? True : False; ?> @@ -48,7 +48,7 @@ - + @@ -110,27 +110,32 @@
- - "> - Username of staff member + + " > + Name of staff
- - "> - Username of staff name + + " > + Staff description
- - " > + Username of staff +
+
+ + - Location of staff member + Staff category
- - " > + Email of staff member +
+
+ + " > + iotJumpWay Location street address +
+
+ + " > + iotJumpWay Location address locality +
+
+ + " > + iotJumpWay Location post code +
+
+ + " > + UID of staff member's NFC card/fob/implant +
+ +
+ + +
+ +
+
+
+ + - iotJumpWay application + Location of staff
-
- - "> - "> - "> - +
+ + > + Is staff member an admin?
-
-
- - - Photo of staff member + + > + Is staff member has patients access?
- - "> - UID of staff member's NFC card/fob/implant + + > + Is staff member cancelled?
- - > - Is staff member an admin? + +

- - > - Does staff member has patient access? + +

- - > - Is staff member cancelled? + +

@@ -209,7 +240,7 @@
User History
- +
@@ -221,20 +252,40 @@ ID Action + Receipt Time retrieveHistory($Staffer["id"], 5); + $history = $Staff->retrieveHistory($Staffer["context"]["Data"]["uid"]["value"], 5); if(count($history)): foreach($history as $key => $value): + if($value["uid"]): + $user = $_GeniSysAi->getUser($value["uid"]); + $userDetails = "User ID #" . $value["uid"] . " (" . $user["name"] . ") "; + endif; ?> # - + + + + + /Applications//Transaction/"># + + NA + + + @@ -255,7 +306,7 @@
User Transactions
- +
@@ -274,15 +325,15 @@ retrieveTransactions($Staffer["id"], 5); + $transactions = $Staff->retrieveTransactions($Staffer["context"]["Data"]["uid"]["value"], 5); if(count($transactions)): foreach($transactions as $key => $value): ?> # - - _GeniSys->_helpers->oDecrypt($value["hash"]);?> + + /Applications//Transaction/"># @@ -303,7 +354,7 @@
User iotJumpWay Application Statuses
- +
@@ -322,15 +373,14 @@ retrieveStatuses($Staffer["aid"], 5); + $Statuses = $Staff->retrieveStatuses($Staffer["context"]["Data"]["aid"]["entity"], 5); if($Statuses["Response"] == "OK"): foreach($Statuses["ResponseData"] as $key => $value): - $location = $iotJumpWay->getLocation($value->Location); ?> #_id;?> - Location: #Location;?> - + Location: #Location;?> Status;?> Time;?> @@ -348,28 +398,30 @@
+
-
Staff Application #
+
Staff Application #
Online Offline
-  %    -  %    -  %    -  °C +  %    +  %    +  %    +  %    +  °C
- " style="width: 100%; !important;" /> + " style="width: 100%; !important;" />
@@ -388,7 +440,7 @@ class="fa fa-refresh"> Reset API Key
-

+

@@ -401,7 +453,7 @@ class="fa fa-refresh"> Reset API Key
-

+

@@ -415,13 +467,34 @@ class="fa fa-refresh"> Reset MQTT Password
-

_helpers->oDecrypt($Staffer["mqttu"]); ?>

+

_helpers->oDecrypt($Application["context"]["Data"]["mqtt"]["username"]); ?>

-

_helpers->oDecrypt($Staffer["mqttp"]); ?> +

_helpers->oDecrypt($Application["context"]["Data"]["mqtt"]["password"]); ?> +

+
+
+
+
+
+
+
+
+ +
+ +
+

_helpers->oDecrypt($Application["context"]["Data"]["amqp"]["username"]) : ""; ?>

+
+
+
+ +
+

_helpers->oDecrypt($Application["context"]["Data"]["amqp"]["password"]) : ""; ?> +

Last Updated:

@@ -429,6 +502,7 @@ class="fa fa-refresh"> Reset MQTT Password
+
@@ -453,7 +527,7 @@ class="fa fa-refresh"> Reset MQTT Password
function initMap() { - var latlng = new google.maps.LatLng("", ""); + var latlng = new google.maps.LatLng("", ""); var map = new google.maps.Map(document.getElementById('map1'), { zoom: 10, center: latlng diff --git a/Root/var/www/html/Hospital/Staff/index.php b/Root/var/www/html/Hospital/Staff/index.php index ae1a5e4..ef5b16e 100644 --- a/Root/var/www/html/Hospital/Staff/index.php +++ b/Root/var/www/html/Hospital/Staff/index.php @@ -1,9 +1,9 @@ "HIS", - "SubPageID" => "Staff", - "LowPageID" => "List" + "PageID" => "HIS", + "SubPageID" => "Staff", + "LowPageID" => "List" ]; include dirname(__FILE__) . '/../../../Classes/Core/init.php'; @@ -19,142 +19,160 @@ - - - - - <?=$_GeniSys->_confs["meta_title"]; ?> - " /> - - - - - - - - - - - - - - - + + + + + <?=$_GeniSys->_confs["meta_title"]; ?> + " /> + + + + + + + + + + + + + + + -
-
-
- -
- - - - - -
-
- - - -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-
-
Hospital Staff
-
-
-
-
-
-
-
-
- - - - - - - - - - - - - $value): +
+
+
+ +
+ + + + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
HIAS Staff
+
+
+
+
+
+
+ +

The HIAS staff area allows you to create and manage users that have access to the HIAS network. Each user has a connected iotJumpWay application that provides the creditials and permissions to access the UI and store data in the HIAS Blockchain.

+ +
+
+
+
+
+
+
Hospital Staff
+
+
+
+
+
+
+
+
+
IDPHOTODETAILSSTATUSACTION
+ + + + + + + + + + + + $value): ?> - - - - - - - - - + + + + + + + + + ?> - -
IDPHOTODETAILSSTATUSACTION
#" style="max-width: 100px; !important;" /> - Name:
- Admin: -
-
"> - -
-
"> Edit
#" style="max-width: 100px; !important;" /> + CANCELLED

" : "";?> + Name:
+ Admin: +
+
"> + +
+
"> Edit
-
-
-
-
-
-
-
+ + +
+
+
+
+
+
+
+
-
+
- + -
+
- + - - + + diff --git a/Root/var/www/html/Includes/Footer.php b/Root/var/www/html/Includes/Footer.php index dd5b936..46a74c9 100644 --- a/Root/var/www/html/Includes/Footer.php +++ b/Root/var/www/html/Includes/Footer.php @@ -1,9 +1,9 @@ - + \ No newline at end of file diff --git a/Root/var/www/html/Includes/JS.php b/Root/var/www/html/Includes/JS.php index 93464fb..685425a 100644 --- a/Root/var/www/html/Includes/JS.php +++ b/Root/var/www/html/Includes/JS.php @@ -1,28 +1,28 @@ - - - + - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + \ No newline at end of file diff --git a/Root/var/www/html/Includes/LeftNav.php b/Root/var/www/html/Includes/LeftNav.php index 39b64a4..57949b8 100644 --- a/Root/var/www/html/Includes/LeftNav.php +++ b/Root/var/www/html/Includes/LeftNav.php @@ -62,42 +62,29 @@

  • - ">
    Security
    -
  • ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/Root/var/www/html/Media/vendor/datatables-plugins/dataTables.bootstrap.css b/Root/var/www/html/Media/vendor/datatables-plugins/dataTables.bootstrap.css new file mode 100644 index 0000000..f65e264 --- /dev/null +++ b/Root/var/www/html/Media/vendor/datatables-plugins/dataTables.bootstrap.css @@ -0,0 +1,314 @@ +div.dataTables_length label { + font-weight: normal; + text-align: left; + white-space: nowrap; +} + +div.dataTables_length select { + width: 75px; + display: inline-block; +} + +div.dataTables_filter { + text-align: right; +} + +div.dataTables_filter label { + font-weight: normal; + white-space: nowrap; + text-align: left; +} + +div.dataTables_filter input { + margin-left: 0.5em; + display: inline-block; +} + +div.dataTables_info { + padding-top: 8px; + white-space: nowrap; +} + +div.dataTables_paginate { + margin: 0; + white-space: nowrap; + text-align: right; +} + +div.dataTables_paginate ul.pagination { + margin: 2px 0; + white-space: nowrap; +} + +@media screen and (max-width: 767px) { + div.dataTables_length, + div.dataTables_filter, + div.dataTables_info, + div.dataTables_paginate { + text-align: center; + } +} + + +table.dataTable td, +table.dataTable th { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + + +table.dataTable { + clear: both; + margin-top: 6px !important; + margin-bottom: 6px !important; + max-width: none !important; +} + +table.dataTable thead .sorting, +table.dataTable thead .sorting_asc, +table.dataTable thead .sorting_desc, +table.dataTable thead .sorting_asc_disabled, +table.dataTable thead .sorting_desc_disabled { + cursor: pointer; +} + +table.dataTable thead .sorting { background: url('../images/sort_both.png') no-repeat center right; } +table.dataTable thead .sorting_asc { background: url('../images/sort_asc.png') no-repeat center right; } +table.dataTable thead .sorting_desc { background: url('../images/sort_desc.png') no-repeat center right; } + +table.dataTable thead .sorting_asc_disabled { background: url('../images/sort_asc_disabled.png') no-repeat center right; } +table.dataTable thead .sorting_desc_disabled { background: url('../images/sort_desc_disabled.png') no-repeat center right; } + +table.dataTable thead > tr > th { + padding-left: 18px; + padding-right: 18px; +} + +table.dataTable th:active { + outline: none; +} + +/* Scrolling */ +div.dataTables_scrollHead table { + margin-bottom: 0 !important; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +div.dataTables_scrollHead table thead tr:last-child th:first-child, +div.dataTables_scrollHead table thead tr:last-child td:first-child { + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} + +div.dataTables_scrollBody table { + border-top: none; + margin-top: 0 !important; + margin-bottom: 0 !important; +} + +div.dataTables_scrollBody tbody tr:first-child th, +div.dataTables_scrollBody tbody tr:first-child td { + border-top: none; +} + +div.dataTables_scrollFoot table { + margin-top: 0 !important; + border-top: none; +} + +/* Frustratingly the border-collapse:collapse used by Bootstrap makes the column + width calculations when using scrolling impossible to align columns. We have + to use separate + */ +table.table-bordered.dataTable { + border-collapse: separate !important; +} +table.table-bordered thead th, +table.table-bordered thead td { + border-left-width: 0; + border-top-width: 0; +} +table.table-bordered tbody th, +table.table-bordered tbody td { + border-left-width: 0; + border-bottom-width: 0; +} +table.table-bordered th:last-child, +table.table-bordered td:last-child { + border-right-width: 0; +} +div.dataTables_scrollHead table.table-bordered { + border-bottom-width: 0; +} + + + + +/* + * TableTools styles + */ +.table.dataTable tbody tr.active td, +.table.dataTable tbody tr.active th { + background-color: #08C; + color: white; +} + +.table.dataTable tbody tr.active:hover td, +.table.dataTable tbody tr.active:hover th { + background-color: #0075b0 !important; +} + +.table.dataTable tbody tr.active th > a, +.table.dataTable tbody tr.active td > a { + color: white; +} + +.table-striped.dataTable tbody tr.active:nth-child(odd) td, +.table-striped.dataTable tbody tr.active:nth-child(odd) th { + background-color: #017ebc; +} + +table.DTTT_selectable tbody tr { + cursor: pointer; +} + +div.DTTT .btn:hover { + text-decoration: none !important; +} + +ul.DTTT_dropdown.dropdown-menu { + z-index: 2003; +} + +ul.DTTT_dropdown.dropdown-menu a { + color: #333 !important; /* needed only when demo_page.css is included */ +} + +ul.DTTT_dropdown.dropdown-menu li { + position: relative; +} + +ul.DTTT_dropdown.dropdown-menu li:hover a { + background-color: #0088cc; + color: white !important; +} + +div.DTTT_collection_background { + z-index: 2002; +} + +/* TableTools information display */ +div.DTTT_print_info { + position: fixed; + top: 50%; + left: 50%; + width: 400px; + height: 150px; + margin-left: -200px; + margin-top: -75px; + text-align: center; + color: #333; + padding: 10px 30px; + opacity: 0.95; + + background-color: white; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + + -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.5); + box-shadow: 0 3px 7px rgba(0, 0, 0, 0.5); +} + +div.DTTT_print_info h6 { + font-weight: normal; + font-size: 28px; + line-height: 28px; + margin: 1em; +} + +div.DTTT_print_info p { + font-size: 14px; + line-height: 20px; +} + +div.dataTables_processing { + position: absolute; + top: 50%; + left: 50%; + width: 100%; + height: 60px; + margin-left: -50%; + margin-top: -25px; + padding-top: 20px; + padding-bottom: 20px; + text-align: center; + font-size: 1.2em; + background-color: white; + background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255,255,255,0)), color-stop(25%, rgba(255,255,255,0.9)), color-stop(75%, rgba(255,255,255,0.9)), color-stop(100%, rgba(255,255,255,0))); + background: -webkit-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%); + background: -moz-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%); + background: -ms-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%); + background: -o-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%); + background: linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%); +} + + + +/* + * FixedColumns styles + */ +div.DTFC_LeftHeadWrapper table, +div.DTFC_LeftFootWrapper table, +div.DTFC_RightHeadWrapper table, +div.DTFC_RightFootWrapper table, +table.DTFC_Cloned tr.even { + background-color: white; + margin-bottom: 0; +} + +div.DTFC_RightHeadWrapper table , +div.DTFC_LeftHeadWrapper table { + border-bottom: none !important; + margin-bottom: 0 !important; + border-top-right-radius: 0 !important; + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} + +div.DTFC_RightHeadWrapper table thead tr:last-child th:first-child, +div.DTFC_RightHeadWrapper table thead tr:last-child td:first-child, +div.DTFC_LeftHeadWrapper table thead tr:last-child th:first-child, +div.DTFC_LeftHeadWrapper table thead tr:last-child td:first-child { + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} + +div.DTFC_RightBodyWrapper table, +div.DTFC_LeftBodyWrapper table { + border-top: none; + margin: 0 !important; +} + +div.DTFC_RightBodyWrapper tbody tr:first-child th, +div.DTFC_RightBodyWrapper tbody tr:first-child td, +div.DTFC_LeftBodyWrapper tbody tr:first-child th, +div.DTFC_LeftBodyWrapper tbody tr:first-child td { + border-top: none; +} + +div.DTFC_RightFootWrapper table, +div.DTFC_LeftFootWrapper table { + border-top: none; + margin-top: 0 !important; +} + + +/* + * FixedHeader styles + */ +div.FixedHeader_Cloned table { + margin: 0 !important +} + diff --git a/Root/var/www/html/Media/vendor/datatables-plugins/dataTables.bootstrap.js b/Root/var/www/html/Media/vendor/datatables-plugins/dataTables.bootstrap.js new file mode 100644 index 0000000..2c6959d --- /dev/null +++ b/Root/var/www/html/Media/vendor/datatables-plugins/dataTables.bootstrap.js @@ -0,0 +1,186 @@ +/*! DataTables Bootstrap 3 integration + * ©2011-2014 SpryMedia Ltd - datatables.net/license + */ + +/** + * DataTables integration for Bootstrap 3. This requires Bootstrap 3 and + * DataTables 1.10 or newer. + * + * This file sets the defaults and adds options to DataTables to style its + * controls using Bootstrap. See http://datatables.net/manual/styling/bootstrap + * for further information. + */ +(function(window, document, undefined){ + +var factory = function( $, DataTable ) { +"use strict"; + + +/* Set the defaults for DataTables initialisation */ +$.extend( true, DataTable.defaults, { + dom: + "<'row'<'col-sm-6'l><'col-sm-6'f>>" + + "<'row'<'col-sm-12'tr>>" + + "<'row'<'col-sm-6'i><'col-sm-6'p>>", + renderer: 'bootstrap' +} ); + + +/* Default class modification */ +$.extend( DataTable.ext.classes, { + sWrapper: "dataTables_wrapper form-inline dt-bootstrap", + sFilterInput: "form-control input-sm", + sLengthSelect: "form-control input-sm" +} ); + + +/* Bootstrap paging button renderer */ +DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, buttons, page, pages ) { + var api = new DataTable.Api( settings ); + var classes = settings.oClasses; + var lang = settings.oLanguage.oPaginate; + var btnDisplay, btnClass; + + var attach = function( container, buttons ) { + var i, ien, node, button; + var clickHandler = function ( e ) { + e.preventDefault(); + if ( !$(e.currentTarget).hasClass('disabled') ) { + api.page( e.data.action ).draw( false ); + } + }; + + for ( i=0, ien=buttons.length ; i 0 ? + '' : ' disabled'); + break; + + case 'previous': + btnDisplay = lang.sPrevious; + btnClass = button + (page > 0 ? + '' : ' disabled'); + break; + + case 'next': + btnDisplay = lang.sNext; + btnClass = button + (page < pages-1 ? + '' : ' disabled'); + break; + + case 'last': + btnDisplay = lang.sLast; + btnClass = button + (page < pages-1 ? + '' : ' disabled'); + break; + + default: + btnDisplay = button + 1; + btnClass = page === button ? + 'active' : ''; + break; + } + + if ( btnDisplay ) { + node = $('
  • ', { + 'class': classes.sPageButton+' '+btnClass, + 'aria-controls': settings.sTableId, + 'tabindex': settings.iTabIndex, + 'id': idx === 0 && typeof button === 'string' ? + settings.sTableId +'_'+ button : + null + } ) + .append( $('', { + 'href': '#' + } ) + .html( btnDisplay ) + ) + .appendTo( container ); + + settings.oApi._fnBindAction( + node, {action: button}, clickHandler + ); + } + } + } + }; + + attach( + $(host).empty().html('