forked from meshtastic/firmware
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRemoteHardwareModule.cpp
More file actions
161 lines (135 loc) · 5.3 KB
/
Copy pathRemoteHardwareModule.cpp
File metadata and controls
161 lines (135 loc) · 5.3 KB
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
#include "RemoteHardwareModule.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "RTC.h"
#include "Router.h"
#include "configuration.h"
#include "main.h"
#include <Throttle.h>
#define NUM_GPIOS 64
// Because (FIXME) we currently don't tell API clients status on sent messages
// we need to throttle our sending, so that if a gpio is bouncing up and down we
// don't generate more messages than the net can send. So we limit watch messages to
// a max of one change per 30 seconds
#define WATCH_INTERVAL_MSEC (30 * 1000)
// Tests for access to read from or write to a specified GPIO pin
static bool pinAccessAllowed(uint64_t mask, uint8_t pin)
{
// If undefined pin access is allowed, don't check the pin and just return true
if (moduleConfig.remote_hardware.allow_undefined_pin_access) {
return true;
}
// Test to see if the pin is in the list of allowed pins and return true if found
if (mask & (1ULL << pin)) {
return true;
}
return false;
}
/// Set pin modes for every set bit in a mask
static void pinModes(uint64_t mask, uint8_t mode, uint64_t maskAvailable)
{
for (uint64_t i = 0; i < NUM_GPIOS; i++) {
if (mask & (1ULL << i)) {
if (pinAccessAllowed(maskAvailable, i)) {
pinMode(i, mode);
}
}
}
}
/// Read all the pins mentioned in a mask
static uint64_t digitalReads(uint64_t mask, uint64_t maskAvailable)
{
uint64_t res = 0;
pinModes(mask, INPUT_PULLUP, maskAvailable);
for (uint64_t i = 0; i < NUM_GPIOS; i++) {
uint64_t m = 1ULL << i;
if (mask & m && pinAccessAllowed(maskAvailable, i)) {
if (digitalRead(i)) {
res |= m;
}
}
}
return res;
}
RemoteHardwareModule::RemoteHardwareModule()
: ProtobufModule("remotehardware", meshtastic_PortNum_REMOTE_HARDWARE_APP, &meshtastic_HardwareMessage_msg),
concurrency::OSThread("RemoteHardware")
{
// restrict to the gpio channel for rx
boundChannel = Channels::gpioChannel;
// Pull available pin allowlist from config and build a bitmask out of it for fast comparisons later
for (uint8_t i = 0; i < 4; i++) {
availablePins += 1ULL << moduleConfig.remote_hardware.available_pins[i].gpio_pin;
}
}
bool RemoteHardwareModule::handleReceivedProtobuf(const meshtastic_MeshPacket &req, meshtastic_HardwareMessage *pptr)
{
if (moduleConfig.remote_hardware.enabled) {
auto p = *pptr;
LOG_INFO("Received RemoteHardware type=%d", p.type);
switch (p.type) {
case meshtastic_HardwareMessage_Type_WRITE_GPIOS: {
pinModes(p.gpio_mask, OUTPUT, availablePins);
for (uint8_t i = 0; i < NUM_GPIOS; i++) {
uint64_t mask = 1ULL << i;
if (p.gpio_mask & mask && pinAccessAllowed(availablePins, i)) {
digitalWrite(i, (p.gpio_value & mask) ? 1 : 0);
}
}
break;
}
case meshtastic_HardwareMessage_Type_READ_GPIOS: {
uint64_t res = digitalReads(p.gpio_mask, availablePins);
// Send the reply
meshtastic_HardwareMessage r = meshtastic_HardwareMessage_init_default;
r.type = meshtastic_HardwareMessage_Type_READ_GPIOS_REPLY;
r.gpio_value = res;
r.gpio_mask = p.gpio_mask;
meshtastic_MeshPacket *p2 = allocDataProtobuf(r);
setReplyTo(p2, req);
myReply = p2;
break;
}
case meshtastic_HardwareMessage_Type_WATCH_GPIOS: {
watchGpios = p.gpio_mask;
lastWatchMsec = 0; // Force a new publish soon
previousWatch =
~watchGpios; // generate a 'previous' value which is guaranteed to not match (to force an initial publish)
enabled = true; // Let our thread run at least once
setInterval(2000); // Set a new interval so we'll run soon
LOG_INFO("Now watching GPIOs 0x%llx", watchGpios);
break;
}
case meshtastic_HardwareMessage_Type_READ_GPIOS_REPLY:
case meshtastic_HardwareMessage_Type_GPIOS_CHANGED:
break; // Ignore - we might see our own replies
default:
LOG_ERROR("Hardware operation %d not yet implemented! FIXME", p.type);
break;
}
}
return false;
}
int32_t RemoteHardwareModule::runOnce()
{
if (moduleConfig.remote_hardware.enabled && watchGpios) {
if (!Throttle::isWithinTimespanMs(lastWatchMsec, WATCH_INTERVAL_MSEC)) {
uint64_t curVal = digitalReads(watchGpios, availablePins);
lastWatchMsec = millis();
if (curVal != previousWatch) {
previousWatch = curVal;
LOG_INFO("Broadcast GPIOS 0x%llx changed!", curVal);
// Something changed! Tell the world with a broadcast message
meshtastic_HardwareMessage r = meshtastic_HardwareMessage_init_default;
r.type = meshtastic_HardwareMessage_Type_GPIOS_CHANGED;
r.gpio_value = curVal;
meshtastic_MeshPacket *p = allocDataProtobuf(r);
service->sendToMesh(p);
}
}
} else {
// No longer watching anything - stop using CPU
return disable();
}
return 2000; // Poll our GPIOs every 2000ms
}