mirror of
https://github.com/meshtastic/firmware.git
synced 2025-02-08 05:31:25 +00:00
commit
50ec03229f
@ -1,11 +1,13 @@
|
||||
set -e
|
||||
|
||||
pio run
|
||||
TARG=tbeam
|
||||
|
||||
pio run -e $TARG
|
||||
|
||||
echo uploading to usb1
|
||||
pio run --upload-port /dev/ttyUSB1 -t upload &
|
||||
pio run --upload-port /dev/ttyUSB1 -t upload -e $TARG &
|
||||
|
||||
echo uploading to usb0
|
||||
pio run --upload-port /dev/ttyUSB0 -t upload &
|
||||
pio run --upload-port /dev/ttyUSB0 -t upload -e $TARG &
|
||||
|
||||
wait
|
||||
wait
|
||||
|
@ -4,7 +4,7 @@ You probably don't care about this section - skip to the next one.
|
||||
|
||||
## before next release
|
||||
|
||||
* android speed settings https://github.com/meshtastic/Meshtastic-Android/issues/271
|
||||
* DONE android speed settings https://github.com/meshtastic/Meshtastic-Android/issues/271
|
||||
* fix heltec battery scaling
|
||||
|
||||
* DONE remote admin busted?
|
||||
|
@ -1,23 +1,60 @@
|
||||
# Remote Hardware Service
|
||||
|
||||
FIXME - the following are a collection of notes moved from elsewhere. We need to refactor these notes into actual documentation on the remote-hardware/gpio service.
|
||||
These are 'programmer focused' notes on using the "remote hardware" feature.
|
||||
|
||||
### 1.7.2. New 'no-code-IOT' mini-app
|
||||
Note: This feature uses a preinstalled plugin in the device code and associated commandline flags/classes in the python code. You'll need to be running at least version 1.2.23 (or later) of the python and device code to use this feature.
|
||||
|
||||
Add a new 'remote GPIO/serial port/SPI/I2C access' mini-app. This new standard app would use the MQTT messaging layer to let users (developers that don't need to write device code) do basic (potentially dangerous) operations remotely.
|
||||
You can get the latest python tool/library with "pip3 install --upgrade meshtastic" on Windows/Linux/OS-X.
|
||||
|
||||
#### 1.7.2.1. Supported operations in the initial release
|
||||
|
||||
Initially supported features for no-code-IOT.
|
||||
## Supported operations in the initial release
|
||||
|
||||
- Set any GPIO
|
||||
- Read any GPIO
|
||||
- Receive notification of changes in any GPIO.
|
||||
|
||||
#### 1.7.2.2. Supported operations eventually
|
||||
## Setup
|
||||
|
||||
General ideas for no-code IOT.
|
||||
GPIO access is fundamentally 'dangerous' because invalid options can physically burn-up hardware. To prevent access from untrusted users you must first make a "gpio" channel that is used for authenticated access to this feature. You'll need to install this channel on both the local and remote node.
|
||||
|
||||
- Subscribe for notification of GPIO input status change (i.e. when pin goes low, send my app a message)
|
||||
- Write/read N bytes over I2C/SPI bus Y (as one atomic I2C/SPI transaction)
|
||||
- Send N bytes out serial port Z
|
||||
- Subscribe for notification for when regex X matches the bytes that were received on serial port Z
|
||||
The procedure using the python command line tool is:
|
||||
|
||||
1. Connect local device via USB
|
||||
2. "meshtastic --ch-add admin; meshtastic --info" thn copy the (long) "Complete URL" that info printed
|
||||
3. Connect remote device via USB (or use the remote admin feature to reach it through the mesh, but that's beyond the scope of this tutorial)
|
||||
4. "meshtastic --seturl theurlyoucopiedinstep2"
|
||||
|
||||
Now both devices can talk over the "gpio" channel.
|
||||
|
||||
## Doing GPIO operations
|
||||
|
||||
Here's some examples using the command line tool.
|
||||
|
||||
## Using GPIOs from python
|
||||
|
||||
You can programmatically do operations from your own python code by using the meshtastic "RemoteHardwareClient" class - see the python documentation for more details.
|
||||
|
||||
Writing a GPIO
|
||||
```
|
||||
meshtastic --port /dev/ttyUSB0 --gpio-wrb 4 1 --dest \!28979058
|
||||
Connected to radio
|
||||
Writing GPIO mask 0x10 with value 0x10 to !28979058
|
||||
```
|
||||
|
||||
Reading a GPIO
|
||||
```
|
||||
meshtastic --port /dev/ttyUSB0 --gpio-rd 0x10 --dest \!28979058
|
||||
Connected to radio
|
||||
Reading GPIO mask 0x10 from !28979058
|
||||
GPIO read response gpio_value=16
|
||||
```
|
||||
|
||||
Watching for GPIO changes:
|
||||
```
|
||||
meshtastic --port /dev/ttyUSB0 --gpio-watch 0x10 --dest \!28979058
|
||||
Connected to radio
|
||||
Watching GPIO mask 0x10 from !28979058
|
||||
Received RemoteHardware typ=GPIOS_CHANGED, gpio_value=16
|
||||
Received RemoteHardware typ=GPIOS_CHANGED, gpio_value=0
|
||||
Received RemoteHardware typ=GPIOS_CHANGED, gpio_value=16
|
||||
< press ctrl-c to exit >
|
||||
```
|
@ -9,7 +9,7 @@
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[platformio]
|
||||
;default_envs = tbeam
|
||||
default_envs = tbeam
|
||||
;default_envs = tbeam0.7
|
||||
;default_envs = heltec
|
||||
;default_envs = tlora-v1
|
||||
@ -18,7 +18,7 @@
|
||||
;default_envs = lora-relay-v1 # nrf board
|
||||
;default_envs = eink
|
||||
;default_envs = nrf52840dk-geeksville
|
||||
default_envs = native # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you'd like to change the default to something like lora-relay-v1 put that here
|
||||
;default_envs = native # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you'd like to change the default to something like lora-relay-v1 put that here
|
||||
|
||||
[common]
|
||||
; common is not currently used
|
||||
@ -397,4 +397,4 @@ lib_deps =
|
||||
;extends = esp32_base
|
||||
;board = genieblocks_lora
|
||||
;build_flags =
|
||||
; ${esp32_base.build_flags} -D GENIEBLOCKS
|
||||
; ${esp32_base.build_flags} -D GENIEBLOCKS
|
||||
|
@ -86,8 +86,10 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
|
||||
/// We only call plugins that are interested in the packet (and the message is destined to us or we are promiscious)
|
||||
bool wantsPacket = (isDecoded || pi.encryptedOk) && (pi.isPromiscuous || toUs) && pi.wantPacket(&mp);
|
||||
|
||||
DEBUG_MSG("Plugin %s wantsPacket=%d\n", pi.name, wantsPacket);
|
||||
assert(!pi.myReply); // If it is !null it means we have a bug, because it should have been sent the previous time
|
||||
|
||||
if (wantsPacket) {
|
||||
// DEBUG_MSG("Plugin %s wantsPacket=%d\n", pi.name, wantsPacket);
|
||||
pluginFound = true;
|
||||
|
||||
/// received channel (or NULL if not decoded)
|
||||
@ -124,6 +126,14 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
|
||||
} else {
|
||||
DEBUG_MSG("Plugin %s considered\n", pi.name);
|
||||
}
|
||||
|
||||
// If the requester didn't ask for a response we might need to discard unused replies to prevent memory leaks
|
||||
if (pi.myReply) {
|
||||
DEBUG_MSG("Discarding an unneeded response\n");
|
||||
packetPool.release(pi.myReply);
|
||||
pi.myReply = NULL;
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
DEBUG_MSG("Plugin %s handled and skipped other processing\n", pi.name);
|
||||
break;
|
||||
@ -136,7 +146,7 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
|
||||
|
||||
if (mp.decoded.want_response && toUs) {
|
||||
if (currentReply) {
|
||||
DEBUG_MSG("Sending response\n");
|
||||
printPacket("Sending response", currentReply);
|
||||
service.sendToMesh(currentReply);
|
||||
currentReply = NULL;
|
||||
} else {
|
||||
@ -154,6 +164,13 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
|
||||
DEBUG_MSG("No plugins interested in portnum=%d\n", mp.decoded.portnum);
|
||||
}
|
||||
|
||||
MeshPacket *MeshPlugin::allocReply()
|
||||
{
|
||||
auto r = myReply;
|
||||
myReply = NULL; // Only use each reply once
|
||||
return r;
|
||||
}
|
||||
|
||||
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
|
||||
* so that subclasses can (optionally) send a response back to the original sender. Implementing this method
|
||||
* is optional
|
||||
@ -176,7 +193,7 @@ void MeshPlugin::sendResponse(const MeshPacket &req)
|
||||
void setReplyTo(MeshPacket *p, const MeshPacket &to)
|
||||
{
|
||||
assert(p->which_payloadVariant == MeshPacket_decoded_tag); // Should already be set by now
|
||||
p->to = getFrom(&to);
|
||||
p->to = getFrom(&to); // Make sure that if we are sending to the local node, we use our local node addr, not 0
|
||||
p->channel = to.channel; // Use the same channel that the request came in on
|
||||
|
||||
// No need for an ack if we are just delivering locally (it just generates an ignored ack)
|
||||
|
@ -69,6 +69,11 @@ class MeshPlugin
|
||||
*/
|
||||
static const MeshPacket *currentRequest;
|
||||
|
||||
/**
|
||||
* If your handler wants to send a response, simply set currentReply and it will be sent at the end of response handling.
|
||||
*/
|
||||
MeshPacket *myReply = NULL;
|
||||
|
||||
/**
|
||||
* Initialize your plugin. This setup function is called once after all hardware and mesh protocol layers have
|
||||
* been initialized
|
||||
@ -87,8 +92,12 @@ class MeshPlugin
|
||||
virtual bool handleReceived(const MeshPacket &mp) { return false; }
|
||||
|
||||
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
|
||||
* so that subclasses can (optionally) send a response back to the original sender. */
|
||||
virtual MeshPacket *allocReply() { return NULL; }
|
||||
* so that subclasses can (optionally) send a response back to the original sender.
|
||||
*
|
||||
* Note: most implementers don't need to override this, instead: If while handling a request you have a reply, just set
|
||||
* the protected reply field in this instance.
|
||||
* */
|
||||
virtual MeshPacket *allocReply();
|
||||
|
||||
/***
|
||||
* @return true if you want to be alloced a UI screen frame
|
||||
@ -106,6 +115,7 @@ class MeshPlugin
|
||||
* the RoutingPlugin to avoid sending redundant acks
|
||||
*/
|
||||
static MeshPacket *currentReply;
|
||||
|
||||
friend class ReliableRouter;
|
||||
|
||||
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
|
||||
|
@ -75,11 +75,6 @@ void MQTT::reconnect()
|
||||
enabled = true; // Start running background process again
|
||||
runASAP = true;
|
||||
|
||||
static char subsStr[64]; /* We keep this static because the mqtt lib
|
||||
might not be copying it */
|
||||
// snprintf(subsStr, sizeof(subsStr), "/ezd/todev/%s/#", clientId);
|
||||
// pubSub.subscribe(subsStr, 1); // we use qos 1 because we don't want to miss messages
|
||||
|
||||
/// FIXME, include more information in the status text
|
||||
bool ok = pubSub.publish(myStatus.c_str(), "online", true);
|
||||
DEBUG_MSG("published %d\n", ok);
|
||||
|
@ -19,7 +19,7 @@ void AdminPlugin::handleGetChannel(const MeshPacket &req, uint32_t channelIndex)
|
||||
AdminMessage r = AdminMessage_init_default;
|
||||
r.get_channel_response = channels.getByIndex(channelIndex);
|
||||
r.which_variant = AdminMessage_get_channel_response_tag;
|
||||
reply = allocDataProtobuf(r);
|
||||
myReply = allocDataProtobuf(r);
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ void AdminPlugin::handleGetRadio(const MeshPacket &req)
|
||||
r.get_radio_response.preferences.ls_secs = getPref_ls_secs();
|
||||
|
||||
r.which_variant = AdminMessage_get_radio_response_tag;
|
||||
reply = allocDataProtobuf(r);
|
||||
myReply = allocDataProtobuf(r);
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ bool AdminPlugin::handleReceivedProtobuf(const MeshPacket &mp, const AdminMessag
|
||||
case AdminMessage_set_channel_tag:
|
||||
DEBUG_MSG("Client is setting channel %d\n", r->set_channel.index);
|
||||
if (r->set_channel.index < 0 || r->set_channel.index >= (int)MAX_NUM_CHANNELS)
|
||||
reply = allocErrorResponse(Routing_Error_BAD_REQUEST, &mp);
|
||||
myReply = allocErrorResponse(Routing_Error_BAD_REQUEST, &mp);
|
||||
else
|
||||
handleSetChannel(r->set_channel);
|
||||
break;
|
||||
@ -66,7 +66,7 @@ bool AdminPlugin::handleReceivedProtobuf(const MeshPacket &mp, const AdminMessag
|
||||
uint32_t i = r->get_channel_request - 1;
|
||||
DEBUG_MSG("Client is getting channel %u\n", i);
|
||||
if (i >= MAX_NUM_CHANNELS)
|
||||
reply = allocErrorResponse(Routing_Error_BAD_REQUEST, &mp);
|
||||
myReply = allocErrorResponse(Routing_Error_BAD_REQUEST, &mp);
|
||||
else
|
||||
handleGetChannel(mp, i);
|
||||
break;
|
||||
@ -141,13 +141,6 @@ void AdminPlugin::handleSetRadio(const RadioConfig &r)
|
||||
service.reloadConfig();
|
||||
}
|
||||
|
||||
MeshPacket *AdminPlugin::allocReply()
|
||||
{
|
||||
auto r = reply;
|
||||
reply = NULL; // Only use each reply once
|
||||
return r;
|
||||
}
|
||||
|
||||
AdminPlugin::AdminPlugin() : ProtobufPlugin("Admin", PortNum_ADMIN_APP, AdminMessage_fields)
|
||||
{
|
||||
// restrict to the admin channel for rx
|
||||
|
@ -6,8 +6,6 @@
|
||||
*/
|
||||
class AdminPlugin : public ProtobufPlugin<AdminMessage>
|
||||
{
|
||||
MeshPacket *reply = NULL;
|
||||
|
||||
public:
|
||||
/** Constructor
|
||||
* name is for debugging output
|
||||
@ -21,10 +19,6 @@ class AdminPlugin : public ProtobufPlugin<AdminMessage>
|
||||
*/
|
||||
virtual bool handleReceivedProtobuf(const MeshPacket &mp, const AdminMessage *p);
|
||||
|
||||
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
|
||||
* so that subclasses can (optionally) send a response back to the original sender. */
|
||||
virtual MeshPacket *allocReply();
|
||||
|
||||
private:
|
||||
void handleSetOwner(const User &o);
|
||||
void handleSetChannel(const Channel &cc);
|
||||
|
@ -10,12 +10,13 @@
|
||||
|
||||
// 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
|
||||
// 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)
|
||||
|
||||
/// Set pin modes for every set bit in a mask
|
||||
static void pinModes(uint64_t mask, uint8_t mode) {
|
||||
static void pinModes(uint64_t mask, uint8_t mode)
|
||||
{
|
||||
for (uint8_t i = 0; i < NUM_GPIOS; i++) {
|
||||
if (mask & (1 << i)) {
|
||||
pinMode(i, mode);
|
||||
@ -24,7 +25,8 @@ static void pinModes(uint64_t mask, uint8_t mode) {
|
||||
}
|
||||
|
||||
/// Read all the pins mentioned in a mask
|
||||
static uint64_t digitalReads(uint64_t mask) {
|
||||
static uint64_t digitalReads(uint64_t mask)
|
||||
{
|
||||
uint64_t res = 0;
|
||||
|
||||
pinModes(mask, INPUT_PULLUP);
|
||||
@ -40,10 +42,9 @@ static uint64_t digitalReads(uint64_t mask) {
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
RemoteHardwarePlugin::RemoteHardwarePlugin()
|
||||
: ProtobufPlugin("remotehardware", PortNum_REMOTE_HARDWARE_APP, HardwareMessage_fields),
|
||||
concurrency::OSThread("remotehardware")
|
||||
: ProtobufPlugin("remotehardware", PortNum_REMOTE_HARDWARE_APP, HardwareMessage_fields), concurrency::OSThread(
|
||||
"remotehardware")
|
||||
{
|
||||
}
|
||||
|
||||
@ -69,26 +70,26 @@ bool RemoteHardwarePlugin::handleReceivedProtobuf(const MeshPacket &req, const H
|
||||
|
||||
case HardwareMessage_Type_READ_GPIOS: {
|
||||
// Print notification to LCD screen
|
||||
if(screen)
|
||||
if (screen)
|
||||
screen->print("Read GPIOs\n");
|
||||
|
||||
uint64_t res = digitalReads(p.gpio_mask);
|
||||
|
||||
// Send the reply
|
||||
HardwareMessage reply = HardwareMessage_init_default;
|
||||
reply.typ = HardwareMessage_Type_READ_GPIOS_REPLY;
|
||||
reply.gpio_value = res;
|
||||
MeshPacket *p = allocDataProtobuf(reply);
|
||||
HardwareMessage r = HardwareMessage_init_default;
|
||||
r.typ = HardwareMessage_Type_READ_GPIOS_REPLY;
|
||||
r.gpio_value = res;
|
||||
MeshPacket *p = allocDataProtobuf(r);
|
||||
setReplyTo(p, req);
|
||||
service.sendToMesh(p);
|
||||
myReply = p;
|
||||
break;
|
||||
}
|
||||
|
||||
case HardwareMessage_Type_WATCH_GPIOS: {
|
||||
watchGpios = p.gpio_mask;
|
||||
lastWatchMsec = 0; // Force a new publish soon
|
||||
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
|
||||
enabled = true; // Let our thread run at least once
|
||||
DEBUG_MSG("Now watching GPIOs 0x%llx\n", watchGpios);
|
||||
break;
|
||||
}
|
||||
@ -101,31 +102,31 @@ bool RemoteHardwarePlugin::handleReceivedProtobuf(const MeshPacket &req, const H
|
||||
DEBUG_MSG("Hardware operation %d not yet implemented! FIXME\n", p.typ);
|
||||
break;
|
||||
}
|
||||
|
||||
return true; // handled
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t RemoteHardwarePlugin::runOnce() {
|
||||
if(watchGpios) {
|
||||
int32_t RemoteHardwarePlugin::runOnce()
|
||||
{
|
||||
if (watchGpios) {
|
||||
uint32_t now = millis();
|
||||
|
||||
if(now - lastWatchMsec >= WATCH_INTERVAL_MSEC) {
|
||||
if (now - lastWatchMsec >= WATCH_INTERVAL_MSEC) {
|
||||
uint64_t curVal = digitalReads(watchGpios);
|
||||
|
||||
if(curVal != previousWatch) {
|
||||
if (curVal != previousWatch) {
|
||||
previousWatch = curVal;
|
||||
DEBUG_MSG("Broadcasting GPIOS 0x%llx changed!\n", curVal);
|
||||
|
||||
// Something changed! Tell the world with a broadcast message
|
||||
HardwareMessage reply = HardwareMessage_init_default;
|
||||
reply.typ = HardwareMessage_Type_GPIOS_CHANGED;
|
||||
reply.gpio_value = curVal;
|
||||
MeshPacket *p = allocDataProtobuf(reply);
|
||||
HardwareMessage r = HardwareMessage_init_default;
|
||||
r.typ = HardwareMessage_Type_GPIOS_CHANGED;
|
||||
r.gpio_value = curVal;
|
||||
MeshPacket *p = allocDataProtobuf(r);
|
||||
service.sendToMesh(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// No longer watching anything - stop using CPU
|
||||
enabled = false;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
[VERSION]
|
||||
major = 1
|
||||
minor = 2
|
||||
build = 20
|
||||
build = 23
|
||||
|
Loading…
Reference in New Issue
Block a user