examples: Add notice about using experimental API
[iotivity.git] / resource / IPCA / samples / ElevatorServer / elevatorserver.cpp
1 /* *****************************************************************
2  *
3  * Copyright 2017 Microsoft
4  *
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  ******************************************************************/
19
20 #include "elevatorserver.h"
21
22 /// This example is using experimental API, so there is no guarantee of support for future release,
23 /// nor any there any guarantee that breaking changes will not occur across releases.
24 #include "experimental/logger.h"
25
26 using namespace OC;
27 using namespace std::placeholders;
28
29 #define TAG                "ElevatorServer.cpp"
30
31 // Secure Virtual Resource database for Iotivity Server
32 // It contains Server's Identity and the PSK credentials
33 // of other devices which the server trusts
34 static char CredFile[] = "ElevatorServerSecurityDB.dat";
35
36 FILE* server_fopen(const char *path, const char *mode)
37 {
38     if (0 == strcmp(path, OC_SECURITY_DB_DAT_FILE_NAME))
39     {
40         return fopen(CredFile, mode);
41     }
42     else
43     {
44         return fopen(path, mode);
45     }
46 }
47
48 //
49 // class ElevatorServer implementation.
50 //
51 ElevatorServer::ElevatorServer() :
52     m_elevatorResourceHandle(nullptr),
53     m_elevatorResourceHandle2(nullptr),
54     m_elevatorResourceHandle3(nullptr),
55     m_elevatorResourceHandle4(nullptr),
56     m_targetFloor(1),
57     m_currentFloor(1),
58     m_direction(ElevatorDirection::Stopped),
59     m_engineThread()
60 {
61 #ifdef SECURED
62     m_displayPasswordCallbackHandle = nullptr;
63 #endif
64 }
65
66 ElevatorServer::~ElevatorServer()
67 {
68     Stop();
69 }
70
71 // Return all properties in response.
72 OCStackResult ElevatorServer::SendResponse(std::shared_ptr<OCResourceRequest> request)
73 {
74     // Values to return.
75     OCRepresentation responseRep;
76     responseRep["x.org.iotivity.CurrentFloor"] = GetCurrentFloor();
77     responseRep["x.org.iotivity.TargetFloor"] = GetTargetFloor();
78     responseRep["x.org.iotivity.Direction"] = (int)GetElevatorDirection();
79
80     // Prepare the response.
81     auto pResponse = std::make_shared<OC::OCResourceResponse>();
82     pResponse->setRequestHandle(request->getRequestHandle());
83     pResponse->setResourceHandle(request->getResourceHandle());
84     pResponse->setResourceRepresentation(responseRep);
85     pResponse->setResponseResult(OC_EH_OK);
86
87     // Send the response.
88     return OCPlatform::sendResponse(pResponse);
89 }
90
91 // Callback handler for elevator resource.
92 OCEntityHandlerResult ElevatorServer::ElevatorEntityHandler(
93                             std::shared_ptr<OCResourceRequest> request)
94 {
95     OCEntityHandlerResult ehResult = OC_EH_ERROR;
96
97     if(request != nullptr)
98     {
99         // Get the request type and request flag
100         std::string requestType = request->getRequestType();
101         int requestFlag = request->getRequestHandlerFlag();
102
103         if(requestFlag & RequestHandlerFlag::RequestFlag)
104         {
105             // If the request type is GET
106             if(requestType == "GET")
107             {
108                 if (SendResponse(request) == OC_STACK_OK)
109                 {
110                     ehResult = OC_EH_OK;
111                 }
112             }
113             else if(requestType == "POST")
114             {
115                 OCRepresentation requestRep = request->getResourceRepresentation();
116
117                 // Target floor can be set.
118                 int targetFloor;
119                 if (requestRep.getValue("x.org.iotivity.TargetFloor", targetFloor))
120                 {
121                     SetTargetFloor(static_cast<int>(targetFloor));
122                 }
123
124                 if(OC_STACK_OK == SendResponse(request))
125                 {
126                     ehResult = OC_EH_OK;
127                 }
128             }
129             else if(requestType == "PUT")
130             {
131                 // not supported.
132             }
133             else if(requestType == "DELETE")
134             {
135                 // not supported.
136             }
137         }
138
139         if(requestFlag & RequestHandlerFlag::ObserverFlag)
140         {
141             // Hold the lock to make sure no iterator is in progress.
142             std::lock_guard<std::mutex> lock(m_elevatorMutex);
143
144             ObservationInfo observationInfo = request->getObservationInfo();
145             if(ObserveAction::ObserveRegister == observationInfo.action)
146             {
147                 OIC_LOG_V(INFO, TAG, "ElevatorEntityHandler(): new observer ID: %d",
148                     observationInfo.obsId);
149                 m_observers.push_back(observationInfo.obsId);
150             }
151             else if(ObserveAction::ObserveUnregister == observationInfo.action)
152             {
153                 OIC_LOG_V(INFO, TAG, "ElevatorEntityHandler(): removing observer ID: %d",
154                     observationInfo.obsId);
155                 m_observers.erase(std::remove(
156                                     m_observers.begin(),
157                                     m_observers.end(),
158                                     observationInfo.obsId),
159                                     m_observers.end());
160             }
161
162             ehResult = OC_EH_OK;
163         }
164     }
165
166     return ehResult;
167 }
168
169 // Copy from std::string to char array.  Return true if source is truncated at dest.
170 bool CopyStringToBuffer(const std::string& source, char* dest, size_t destSize)
171 {
172     bool isTruncated = false;
173     size_t copied = source.copy(dest, destSize, 0);
174     if (copied == destSize)
175     {
176         copied -= 1;    // make room for null
177         isTruncated = true;
178     }
179
180     // std::string copy does not include null.
181     dest[copied] = 0x00;
182     return isTruncated;
183 }
184
185 // Initialize Persistent Storage for security database
186 OCPersistentStorage ps = {server_fopen, fread, fwrite, fclose, unlink};
187
188 bool ElevatorServer::Start(const std::string& elevatorName)
189 {
190     std::lock_guard<std::mutex> lock(m_elevatorMutex);
191
192     // OCPlatform needs only 1 time initialization.
193     static bool OCFInitialized = false;
194     if (false == OCFInitialized)
195     {
196         PlatformConfig Configuration {
197                                 ServiceType::InProc,
198                                 ModeType::Server,
199                                 "0.0.0.0", // By setting to "0.0.0.0", it binds to all available
200                                            // interfaces
201                                 0,         // Uses randomly available port
202                                 QualityOfService::NaQos,
203                                 &ps
204                             };
205
206         OCPlatform::Configure(Configuration);
207         OCFInitialized = true;
208     }
209
210     if (false == m_isRunning)
211     {
212         std::string defaultResourceTypeName("x.org.iotivity.sample.elevator");
213         std::string defaultResourceName("/ipca/sample/elevator");
214
215         m_name = elevatorName;
216
217         // Start with known state.
218         m_targetFloor = m_currentFloor = 1;
219         m_direction = ElevatorDirection::Stopped;
220
221         // Start the engine thread.
222         m_isRunning = true;
223         m_engineThread = std::thread(&ElevatorServer::Engine, this);
224
225         // Device Info.
226         char devName[256];
227         char resTypeName[256];
228         CopyStringToBuffer(m_name, devName, 256);
229         CopyStringToBuffer(defaultResourceTypeName, resTypeName, 256);
230         OCStringLL types { nullptr, resTypeName };
231         char specVersion[] = "0.0.1";
232         OCDeviceInfo deviceInfo = { devName, &types, specVersion, nullptr };
233
234         // Platform Info
235         char platformId[] = "6cb6c994-8c4b-11e6-ae22-56b6b6499611";
236         char manufacturerName[] = "E Manufacturer";
237         char manufacturerUrl[] = "http://www.example.com/elevator";
238         char modelNumber[] = "Elevator Model Number";
239         char dateManufacture[] = "2017-02-28";
240         char platformVersion[] = "Elevator Platform Version";
241         char osVersion[] = "Elevator OS Version";
242         char hardwareVersion[] = "Elevator HW Version";
243         char firmwareVersion[] = "Elevator FW Version";
244         char supportURL[] = "http://www.example.com/elevator/support";
245
246         OCPlatformInfo platformInfo = {
247             platformId,
248             manufacturerName,
249             manufacturerUrl,
250             modelNumber,
251             dateManufacture,
252             platformVersion,
253             osVersion,
254             hardwareVersion,
255             firmwareVersion,
256             supportURL,
257             nullptr};
258
259         // Register elevator's platformInfo, deviceInfo, and resource.
260         if (OC_STACK_OK != OCPlatform::registerPlatformInfo(platformInfo))
261         {
262             return false;
263         }
264
265         if (OC_STACK_OK != OCPlatform::registerDeviceInfo(deviceInfo))
266         {
267             return false;
268         }
269
270 #ifdef SECURED
271         if (OC_STACK_OK != OCSecure::registerDisplayPinCallback(
272                             std::bind(&ElevatorServer::PinDisplayCallback, this, _1, _2),
273                                       &m_displayPasswordCallbackHandle))
274         {
275             return false;
276         }
277 #endif
278         OCStackResult result = OCPlatform::registerResource(
279                                         m_elevatorResourceHandle,
280                                         defaultResourceName,
281                                         defaultResourceTypeName,
282                                         DEFAULT_INTERFACE,
283                                         std::bind(&ElevatorServer::ElevatorEntityHandler, this, _1),
284                                         OC_DISCOVERABLE | OC_OBSERVABLE | OC_SECURE);
285
286         if (result != OC_STACK_OK)
287         {
288             return false;
289         }
290
291         // These extra resources are created but not implemented by the entity handler.
292         // They better reflect real devices supporting multiple resources.
293
294         std::string defaultResourceTypeName2("x.org.iotivity.sample.elevator2");
295         std::string defaultResourceName2("/ipca/sample/elevator2");
296         result = OCPlatform::registerResource(
297                                     m_elevatorResourceHandle2,
298                                     defaultResourceName2,
299                                     defaultResourceTypeName2,
300                                     DEFAULT_INTERFACE,
301                                     std::bind(&ElevatorServer::ElevatorEntityHandler, this, _1),
302                                     OC_DISCOVERABLE | OC_OBSERVABLE | OC_SECURE);
303
304         if (result != OC_STACK_OK)
305         {
306             return false;
307         }
308
309         std::string defaultResourceTypeName3("x.org.iotivity.sample.elevator3");
310         std::string defaultResourceName3("/ipca/sample/elevator3");
311         result = OCPlatform::registerResource(
312                                     m_elevatorResourceHandle3,
313                                     defaultResourceName3,
314                                     defaultResourceTypeName3,
315                                     DEFAULT_INTERFACE,
316                                     std::bind(&ElevatorServer::ElevatorEntityHandler, this, _1),
317                                     OC_DISCOVERABLE | OC_OBSERVABLE | OC_SECURE);
318
319         if (result != OC_STACK_OK)
320         {
321             return false;
322         }
323
324         std::string defaultResourceTypeName4("x.org.iotivity.sample.elevator4");
325         std::string defaultResourceName4("/ipca/sample/elevator4");
326         result = OCPlatform::registerResource(
327                                     m_elevatorResourceHandle4,
328                                     defaultResourceName4,
329                                     defaultResourceTypeName4,
330                                     DEFAULT_INTERFACE,
331                                     std::bind(&ElevatorServer::ElevatorEntityHandler, this, _1),
332                                     OC_DISCOVERABLE | OC_OBSERVABLE | OC_SECURE);
333
334
335         if (result == OC_STACK_OK)
336         {
337             const char* deviceID = OCGetServerInstanceIDString();
338             std::cout << "Elevator server's device ID: ";
339             if (deviceID != nullptr)
340             {
341                  std::cout << deviceID << std::endl;
342             }
343             else
344             {
345                 std::cout << "Unknown" << std::endl;
346             }
347         }
348
349         return (result == OC_STACK_OK);
350     }
351
352     // It's already running.
353     return true;
354 }
355
356 void ElevatorServer::Stop()
357 {
358     std::lock_guard<std::mutex> lock(m_elevatorMutex);
359
360     if (true == m_isRunning)
361     {
362         // Unregister OCF resource.
363         OCPlatform::unregisterResource(m_elevatorResourceHandle);
364         OCPlatform::unregisterResource(m_elevatorResourceHandle2);
365         OCPlatform::unregisterResource(m_elevatorResourceHandle3);
366         OCPlatform::unregisterResource(m_elevatorResourceHandle4);
367         m_elevatorResourceHandle = nullptr;
368         m_elevatorResourceHandle2 = nullptr;
369         m_elevatorResourceHandle3 = nullptr;
370         m_elevatorResourceHandle4 = nullptr;
371
372         // Signal the m_engineThread to stop and wait for it to exit.
373         m_isRunning = false;
374         if (m_engineThread.joinable())
375         {
376             m_engineThread.join();
377         }
378
379 #ifdef SECURED
380         // Unregister the password display callback
381         OCSecure::deregisterDisplayPinCallback(m_displayPasswordCallbackHandle);
382         m_displayPasswordCallbackHandle = nullptr;
383 #endif
384     }
385 }
386
387 void ElevatorServer::SetTargetFloor(int floor)
388 {
389     m_targetFloor = floor;
390 }
391
392 int ElevatorServer::GetTargetFloor()
393 {
394     return m_targetFloor;
395 }
396
397 int ElevatorServer::GetCurrentFloor()
398 {
399     return m_currentFloor;
400 }
401
402 ElevatorDirection ElevatorServer::GetElevatorDirection()
403 {
404     return m_direction;
405 }
406
407 void ElevatorServer::Engine(ElevatorServer* elevator)
408 {
409     const size_t EngineSleepTimeSeconds = 2;
410     std::chrono::seconds engineSleepTime(EngineSleepTimeSeconds);
411
412     while (elevator->m_isRunning == true)
413     {
414         elevator->MoveElevator();
415         std::this_thread::sleep_for(engineSleepTime);
416     }
417 }
418
419 void ElevatorServer::NotifyObservers()
420 {
421     // Local copy of observers so the code below doesn't need to hold the lock when
422     // calling out to notifyListOfObservers.
423     ObservationIds localCopyObservers;
424
425     if (m_observers.size() == 0)
426     {
427         return;
428     }
429     else
430     {
431         std::lock_guard<std::mutex> lock(m_elevatorMutex);
432         localCopyObservers = m_observers;
433     }
434
435     OCRepresentation rep;
436     rep["x.org.iotivity.CurrentFloor"] = GetCurrentFloor();
437     rep["x.org.iotivity.Direction"] = (int)GetElevatorDirection();
438     rep["x.org.iotivity.TargetFloor"] = GetTargetFloor();
439
440     // Prepare the response.
441     auto response = std::make_shared<OC::OCResourceResponse>();
442     response->setResourceRepresentation(rep, DEFAULT_INTERFACE);
443
444     OCStackResult result = OCPlatform::notifyListOfObservers(
445                                 m_elevatorResourceHandle,
446                                 localCopyObservers,
447                                 response);
448
449     if(OC_STACK_NO_OBSERVERS == result)
450     {
451         std::cout << "ElevatorServer:: failed notifyListOfObservers: result = ";
452         std::cout << result << std::endl;
453     }
454 }
455
456 void ElevatorServer::MoveElevator()
457 {
458     int dwTargetFloor = m_targetFloor;
459     int incrementValue;
460
461     if (m_currentFloor == dwTargetFloor)
462     {
463         return;
464     }
465
466     if (m_currentFloor < dwTargetFloor)
467     {
468         m_direction = ElevatorDirection::Up;
469         incrementValue = 1;
470     }
471     else
472     {
473         m_direction = ElevatorDirection::Down;
474         incrementValue = -1;
475     }
476
477     std::cout << "ElevatorServer::MoveElevator() new Direction: " << m_direction << std::endl;
478     NotifyObservers();
479
480     const int DelayBetweenFloorMilliseconds = 10;
481     std::chrono::milliseconds delayBetweenFloor(DelayBetweenFloorMilliseconds);
482
483     while (m_currentFloor != dwTargetFloor)
484     {
485
486         m_currentFloor += incrementValue;
487         NotifyObservers();
488         std::cout << "ElevatorServer::MoveElevator() new CurrentFloor: " << m_currentFloor;
489         std::cout << std::endl;
490         std::this_thread::sleep_for(delayBetweenFloor);
491     }
492
493     m_direction = ElevatorDirection::Stopped;
494     NotifyObservers();
495     std::cout << "ElevatorServer::MoveElevator() new Direction: " << m_direction << std::endl;
496 }
497
498 void ElevatorServer::PinDisplayCallback(char* pinData, size_t pinLength)
499 {
500     if ((nullptr == pinData) || (pinLength == 0))
501     {
502         std::cout << "Invalid pin!" << std::endl;
503         return;
504     }
505
506     std::cout << "============================" << std::endl;
507     std::cout << "    PIN CODE: " << pinData << std::endl;
508     std::cout << "============================" << std::endl;
509 }