-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathdlock13.cpp
244 lines (202 loc) · 7.99 KB
/
dlock13.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
#include <iostream>
#include <atomic>
#include <chrono>
#include <thread>
#include <fstream>
#include <time.h>
#include "msgflo.h"
using namespace std;
// Minimalistic design-by-contracts support
// Throws Exception containing @msg if @predicate is not satisfied
#define PRECONDITION(predicate, msg) \
do { \
if (!(predicate)) { \
throw std::domain_error(std::string("Precondition failed: ") + msg); \
} \
} while(0)
#define POSTCONDITION(predicate, msg) \
do { \
if (!(predicate)) { \
throw std::logic_error(std::string("Postcondition failed: ") + msg); \
} \
} while(0)
struct LockState {
public:
bool isOpen = false;
time_t openUntil = 0;
public:
LockState() : isOpen(false), openUntil(0) {}
LockState(bool o, time_t u) : isOpen(o), openUntil(u) {}
// actually an invariant of this data
bool isValid() const {
if (!isOpen) {
return openUntil == 0; // always 0 if not open
} else {
return (openUntil > 1451606400); // can't be in the past
}
}
bool operator ==(const LockState &b) const {
return this->isOpen == b.isOpen && this->openUntil == b.openUntil;
}
bool operator !=(const LockState &b) const {
return !(*this == b);
}
};
LockState unlock(const LockState currentState, int openSeconds, time_t currentTime, int maxOpenSeconds) {
// preconditions
PRECONDITION(currentState.isValid(), "Current state is not valid");
PRECONDITION(openSeconds > 0, "Time to open is negative or 0");
PRECONDITION(currentTime > 1451606400, "Current time is before the code was made (2016)");
PRECONDITION(maxOpenSeconds > 0, "Maximum time to open is negative or 0");
PRECONDITION(maxOpenSeconds < 10*60, "Maximum time to open is extremely long");
PRECONDITION(openSeconds <= maxOpenSeconds, "Time to open is longer than maximum");
const LockState newState(true, currentTime+openSeconds);
// postconditions
POSTCONDITION(newState.isValid(), "New state is not valid");
POSTCONDITION(newState.isOpen, "Lock did not open");
POSTCONDITION(newState.openUntil > currentState.openUntil, "Lock is not open for longer than before");
POSTCONDITION(newState.openUntil >= currentTime+openSeconds, "Lock is not open for at least @openSeconds");
return newState;
}
LockState tryLock(const LockState currentState, time_t currentTime, int maxOpenSeconds) {
// preconditions
PRECONDITION(currentState.isValid(), "Current state is not valid");
PRECONDITION(currentTime > 1451606400, "Current time is before the code was made (2016)");
PRECONDITION(maxOpenSeconds > 0, "Maximum time to open is negative or 0");
PRECONDITION(maxOpenSeconds < 10*60, "Maximum time to open is extremely long");
// PRECONDITION(currentTime < currentState.+maxOpenSeconds, "Current time forwarded by more than maximum open time");
const bool shouldLock = currentState.isOpen && currentTime >= currentState.openUntil;
const LockState newState = shouldLock ? LockState() : currentState;
// postconditions
POSTCONDITION(newState.isValid(), "New state is not valid");
POSTCONDITION(shouldLock == (newState != currentState), "State changed even though we should not lock");
return newState;
}
static const int MAX_UNLOCK_SECONDS = 2*60;
class DoorLock {
public:
DoorLock(string gpio)
: gpio_path(gpio)
{
}
public:
void initializeWithParticipant(msgflo::Participant *part) {
participant = part;
setState(state);
}
void check() {
try {
const time_t currentTime = time(NULL);
const LockState newState = tryLock(state, currentTime, MAX_UNLOCK_SECONDS);
if (newState != state) {
setState(newState);
POSTCONDITION(!state.isOpen, "Lock open after successful locking");
}
} catch (const std::exception &e) {
cerr << e.what() << endl;
setState(LockState()); // Try to ensure gets locked anyway
send("error", std::string(e.what()));
}
}
virtual void process(string port, msgflo::Message *msg) {
try {
PRECONDITION(port == "open", "Invalid inport name");
const int openSeconds = std::stoi(msg->asString());
cout << "DoorLock.open(" << openSeconds << ")" << endl;
const time_t currentTime = time(NULL);
const LockState newState = unlock(state, openSeconds, currentTime, MAX_UNLOCK_SECONDS);
setState(newState);
POSTCONDITION(state.isOpen, "Lock not open after successful unlocking");
} catch (const std::exception& e) {
// Don't change state, just notify of the problem
cerr << e.what() << endl;
send("error", std::string(e.what()));
}
// always ACK, even errors are considered "done processing"
msg->ack();
}
private:
void send(std::string port, const std::string &data) {
PRECONDITION(participant, "No participant registered in send()");
participant->send(port, data);
}
void setGPIO(const std::string &filepath, std::string value) {
PRECONDITION(filepath.length(), "GPIO filepath is empty");
std::ofstream fs(filepath.c_str());
PRECONDITION(fs, "GPIO filepath cannot be opened");
cout << "DoorLock set GPIO: " << filepath << " to " << value << endl;
fs << value;
fs.close();
POSTCONDITION(!fs.is_open(), "File stream was not closed");
// POSTCONDITION: file now contains @value
}
void setState(LockState s) {
PRECONDITION(s.isValid(), "New state not valid");
// Actualize the state
state = s;
setGPIO(gpio_path, state.isOpen ? "1" : "0");
// Notify about change
const std::string isOpen = std::string(state.isOpen ? "true" : "false");
send("isopen", isOpen);
const std::string openUntil = std::to_string(state.openUntil);
send("openuntil", openUntil);
cout << "DoorLock open " << isOpen << " until " << openUntil << endl;
// POSTCONDITION: door solenoid now reflects new state
}
private:
LockState state;
std::string gpio_path;
msgflo::Participant *participant;
};
int main(int argc, char **argv) {
atomic_bool run(true);
std::string role = "dlock13-1";
if (argc > 1) {
role = argv[1];
}
std::string file = "/sys/class/gpio/gpio11/value";
if (argc > 2) {
file = argv[2];
}
std::string prefix = "/bitraf/door/";
if (argc > 3) {
prefix = argv[3];
}
cout << "Role: " << role << endl;
cout << "File: " << file << endl;
cout << "Prefix: " << prefix << endl;
msgflo::Definition def;
def.role = role;
def.id = role;
def.component = "DoorLock";
def.label = "Door lock for " + role;
// TODO: msgflo-cpp, allows the port descriptions be included
const std::string queuePrefix = prefix + role;
def.inports = {
{"open", "int", queuePrefix+"/open"}, // Open the door for N seconds (maximum 120)
};
def.outports = {
{"isopen", "boolean", queuePrefix+"/isopen"}, // "Whether the door is open or not"
{"openuntil", "int", queuePrefix+"/openuntil"}, // What time the door will be open until (Unix timestamp)
{"error", "string", queuePrefix+"/error"}, // Error from trying to open door
};
auto config = msgflo::EngineConfig();
config.debugOutput(true);
auto engine = msgflo::createEngine(config);
DoorLock dlock(file);
msgflo::Participant *participant = engine->registerParticipant(def, [&](msgflo::Message *msg) {
// TODO: msgflo-cpp, support port name in processing function
dlock.process("open", msg);
});
// TODO: avoid DoorLock needing to know Participant?
dlock.initializeWithParticipant(participant);
std::thread checker([&]()
{
while (run) {
this_thread::sleep_for(chrono::seconds(1));
dlock.check();
}
});
engine->launch();
return EXIT_SUCCESS;
}