timmbogner / Farm-Data-Relay-System

A system that uses ESP-NOW, LoRa, and other protocols to transport sensor data in remote areas without relying on WiFi.
MIT License
485 stars 108 forks source link

[Feature Addition] Gateway/repeater sending battery voltage #114

Closed Stips5 closed 1 year ago

Stips5 commented 1 year ago

As discussed in #112 , there is idea that repeater/gateway could send some value as well (eg. battery voltage, or some other sensor reading)

timmbogner commented 1 year ago

Cool. I'll put my ideas forth and if anyone has feedback, please chime in!

The feature will be to read a voltage via the ADC and do the calculations needed to convert it to a 4.2v scale. I think two voltages would be nice for situations with a solar panel and battery. The ESP8266 only has one ADC, but I will debate adding a second for 32 once the first is implemented. Three or four config parameters should be sufficient: First the ADC pin, and a multiplier that is correlated to the voltage divider of the individual board. Would you prefer that it sends the voltage only when sending other data? That would be easy enough to implement, just add a condition in the ESP-NOW and LoRa sending functions I think. Otherwise an interval (delay) parameter will be needed. The final parameter we'll need the user to provide is a reading ID for the data.

My next concern is the best practice for reading the ADC quickly and without blocking anything. I'm not sure if this is really even a problem yet.

All advice and feedback is appreciated!

nglessner commented 1 year ago

I was thinking about this much more generically. Yes, for my purposes, battery voltage is what I want to send, but I could see others wanting to send temperature/humidity or some other value.

As far as piggybacking on other messages - again, that probably works for my case, but I wonder if FDRS would quickly reach a limit of packet size for ESPNow/Lora.

Stips5 commented 1 year ago

I don't like hardcoding voltage to 4.2 it could be default one,but I would like to customise it as well, eg what if I am using 12 battery. Also I wouldn't make it over ADC only, eg what if I have BMS or somethig giving me capacity percentage. Maybe it could be defined as some kind of header where custom data could be set as temp, voltage ...

timmbogner commented 1 year ago

My concern still lies in the fact that if we open up the gateway to user-customized code, then there's a chance that the code to receive transmissions could be blocked by the user's code, and generally things become more difficult to troubleshoot. The ADC is available on every chip, so I don't mind adding the code to interface with it.

I just moved all of the code from the gateway.ino files into the fdrs_functions.h header, leaving very little code in the .ino. This will make it easier to add experimental elements like this. Check it out in the clean gateway branch.

I'll keep thinking about this. I know what you're looking for, I'm just worried about adding complication to the system. It's really easy to write in the documentation: "Nodes are sensors and controllers; while gateways are only access points." It becomes much harder to explain when "Nodes are sensors and controllers, gateways are access points but can also be sensors, however there are certain limitations to this functionality...".

@Stips5 It's worth noting that the 4.2 isn't actually hardcoded. The system would take the raw ADC value and multiply it by a number specified by the user (which itself would be dependent upon your hardware's voltage divider), and that would convert the raw reading to whatever scale the user wanted. I have an idea of how to deal with a BMS. A pin definition of -1 will indicate there's a BMS in use, and for the multiplier value you would define the function that gets the voltage from the BMS.

@nglessner I hadn't really considered how that would be at scale, but you're right: it could cause a bit of a snowball.

Stips5 commented 1 year ago

Any news, plans?

timmbogner commented 1 year ago

I'm pretty indecisive about this, which is a large part of the delay. At this moment, I'm leaning towards a concept that allows the user to define functions that are called at timed intervals. My idea is that the user can define a function in the setup() of the sketch something like this:

// draft removed, please see below for finalized usage!

So the user can provide a function that returns a DataReading, and it will then submit the returned DataReading as ESP-NOW and LoRa general traffic at the interval provided. I think all of this is possible. Maybe the ability to send it to a certain interface instead of general traffic.

I've been busy working on other things and generally mulling this over. What do you guys think of the function idea?

Stips5 commented 1 year ago

I like it, do you have idea what messenge form will it be like

timmbogner commented 1 year ago

I like it, do you have idea what messenge form will it be like

The message will be sent as a DataReading just like any other incoming sensor value, using the ESPNOWG_ACT (or LORAG_ACT) definition to decide where it goes. I'm still not sure about the mechanics of everything, especially making it simpler to send multiple values at once.

timmbogner commented 1 year ago

Hey @Stips5, I'm finally getting to the point where I can think about exactly how this feature will work. In the newest branch I recently removed the old buffering functionality and replaced it with an internal timer/scheduler, which makes all LoRa transmissions at one constant interval. The same scheduler can be used to tell the gateway to run a user-defined function at a constant interval as well. In the following example, myFunc() will be called every 10 seconds:

#include "fdrs_gateway_config.h"
#include <fdrs_gateway.h>
void myFunc() {
  DBG("Timed Function");
}
void setup() {
  beginFDRS();
  scheduleFDRS(myFunc, 10000);
}

void loop() {
  loopFDRS();
}

In this function you could take a voltage or temperature reading. The next question is how to submit it and where to send it. My current idea is to adapt loadFDRS() and sendFDRS() used for sensors to the gateway, which will enter them to the system just like other incoming data. Next, I'll add an INTERNAL_ACT action to config, which the user could use to direct the data with sendSerial, sendLoRaNbr, etc.

I'll probably mull it over for a bit and add it in the next day or two. Please let me know if you have any input!

Stips5 commented 1 year ago

Looks great! I agree that using existingn FDRS metgods fornsending would be good solution. I am sure you'll make it work great. Cant wait to try it :)

timmbogner commented 1 year ago

First I want to thank you both. Thanks to your suggestions, I ended up changing course completely from my original plan. I think the scheme presented here is far superior!

The gateway INO looks like this:


#include "fdrs_gateway_config.h"
#include <fdrs_gateway.h>
void sendReading() {
  loadFDRS(random(0, 100), HUMIDITY_T, 23);
  sendFDRS();
}

void setup() {
  beginFDRS();
  scheduleFDRS(sendReading, 30000);
}

void loop() {
  loopFDRS();
}

The gateway config will look like this by default:

#define INTERNAL_ACT   sendSerial();

I don't know yet if I'll build something like this into the default examples, or use a separate example to demonstrate the feature.

aviateur17 commented 1 year ago

@timmbogner, now that gateways can send FDRS packets for internal data should we assign the gateways a READING_ID like nodes? Also, should we implement a new type called TIME_T? I know it might be useful to query or report the time of the gateway. Could also use TIME_T to report the time of the nodes. I'm also adding a calculation to report how many seconds the time changes when it is adjusted. Might be useful to have that information as a reading to track how accurate the time is. I'm not sure if the 4 bytes of time_t will fit in a float off hand without looking.

timmbogner commented 1 year ago

I'm not sure about the READING_ID, for now I'll keep it unattached to avoid confusion.

I've been thinking about TIME_T as well. DataReading.d does hold 4 bytes, but I don't think converting the uint32 to a float and back will be ideal. Eliminating floats, at least in transport was actually one of Andreas' first and biggest suggestions before the video, but it was too tricky for me to accomplish with my skills at the time. I've also always had an idea to borrow two bits from the type to use to tell what to cast the data into. That could even be backwards compatible if we say 0b00 is a float.

So for instance: the first two bits of the type are now the data type. So decimal types 0-63 are now the "float version" of what we've already defined. There are currently 30 types, so plenty of room. Types 64-127 are the now 'uint32_t versions', (a humidity sent as an integer would be expressed as type (64 + HUMIDITY_T) ) then probably byte and boolean at 128 and 196. It would be a bit of a waste, for example the boolean versions of most types will never be used. But I've always thought it would be a good way to tackle this issue without defining different data types to specific DataReading types or breaking compatibility.

Anywho... I know you didn't really ask, but that's how that's gonna go.

aviateur17 commented 1 year ago

So without READING_ID defined in the gateway like the node then people will just have to add the variable themselves or manually put in a number into loadFDRS function like: loadFDRS(var, STATUS_T, 111);

In regards to storing time in a float to send the DataReading it seems there is no issue with it. Here's how it is recorded in my MQTT client: image

timmbogner commented 1 year ago

I actually made an example earlier today. I'll think about doing it in config and maybe bringing over the shorter loadFDRS() function. I guess there's no big reason not to.

That's good about times. I just looked it up and with floats we'll have up to 2147483647 in integers. This means we have until the year 2038 to find a different way to go about this.

timmbogner commented 1 year ago

I think there are still issues with this. Floats aren't accurate when they're really high, so we'll lose accuracy on integers (unless I'm missing something).

If I send [{ id: 1, type: 6, data: 1677886843}] to the MQTT gateway, I get {"id":1,"type":6,"data":1677886848,"time":106} from the Node-red debug.

timmbogner commented 1 year ago

@aviateur17 I closed this, let's continue the 'time as a DataReading' convo in the timekeeping discussion thread.