MAJOR UPDATES
- October 4th 2023: “APPENDIX #4: Battery & Voltage Note” added.
- September 16th 2023:
.- New sensor sketch that compiles within program storage space limits (95%).
.- Soil humidity measurement method aligned with Makerfabs LoRaWAN sensor sketch.
.- Calibration code provided.
.- Information on bandgap reference for accurate battery voltage measurement.
.- Sketch enriched with comments for better understanding.
.- I2C_AHT10 library provided ad-hoc with the sketch.
.- Code cosmetics.
.- Enriched post instructions.
.- Appendix on hacked Makerfab soil sensor.
(due to post size limitations this post has been split and so it continues on post#11)
- June 27th 2023: “Appendix #1: Recover a bricked Sensor” added.
- July 08th 2024: “Appendix #5: V2 Better Battery Life Hack” added.
DISCLAIMER
- All information herein (post #1 and post #11) is for documentation purposes and not meant to be used in any device. Deviating from this is to be under your responsibility.
- Makerfabs LoRa Soil moisture sensor is full Open Source (What Upgraded on Lora Soil Moisture Sensor V3?). All hardware and software open at GitHub.
- AG March (LORAWAN SOIL MOISTURE SENSOR | Hackaday.io) is credited for adapting Makerfabs code to LoRaWAN.
ASSUMPTIONS
- You are familiarized with The Things Network (https://www.thethingsnetwork.org/).
- You know how to create an application and devices in The Things Network (TTN).
- You have a LoRaWAN gateway in the nearby (or you have your own).
- You know how to upload a sketch into Arduino or similar microprocessor.
- You have HACS installed on your Home Assistant and the HACS TTN v3 adapter installed.
- You have followed the instructions on how to configure HACS TTN v3 adapter to receive data from TTN.
- You have a Zigbee setup with a power control outlet, e.g IKEA Trådfri Control Outlet.
CAVEAT
- The sketch presented below makes use of LoRaWAN Over-the-Air Activation (OTAA).
THE STORY
I bought two LoRa soil moisture V3 sensors from Makerfabs (Lora Temperature/ Humidity/ Soil Moisture Sensor V3 | Makerfabs) to monitor and water my tomato plants.
The device is featured with LoRa (GitHub - Makerfabs/Lora-Soil-Moisture-Sensor: Lora Soil Moisture Sensor) although I needed LoRaWAN to get the sensor engaged with The Things Network (TTN).
As mentioned in the disclaimer above, I found that AG March developed a LoRaWAN sketch but for the V2 sensor.
The major difference between V1/V2 and V3 is that the latest generates a square wave by the MCU replacing the 555 IC found in version V1/V2. Since all three versions feature a capacitive moisture sensor, it means that the code to calculate the moisture got changed on V3.
I merged the two codes by replacing the soil moisture calculation found in AG March sketch by the one in Makerfab sketch.
THE SETUP
PICTURES
Note: the increase in moisture before reaching 50 is because slight rain.
HA CONFIGURATION
HA receives the values from TTN as string therefore they need to get converted into numbers to get the chronological graph representation of the values. In configuration.yaml
you need to add:
- platform: template
sensors:
soil_moist:
friendly_name: "Soil Moisture"
unit_of_measurement: '%'
value_template: "{{ int(states('sensor.YOUR_SENSOR')) }}"
To trigger the watering:
# Watering during the day only
- alias: "Watering ON when soil humidity level reached"
trigger:
platform: template
value_template: "{{ (states('sensor.soil_hum') | int(0) < 50) }}"
condition:
condition: sun
after: sunrise
before: sunset
before_offset: "-03:00:00"
action:
service: switch.turn_on
entity_id: switch.YOUR_ZIGBEE_CONTROL_OUTLET
# Sunrise watering
- alias: "Morning watering"
trigger:
platform: sun
event: sunrise
condition:
condition: template
value_template: "{{ (states('sensor.soil_hum') | int(0) < 50) }}"
action:
service: switch.turn_on
entity_id: switch.YOUR_ZIGBEE_CONTROL_OUTLET
- alias: "Watering OFF after 5 minutes"
trigger:
platform: state
entity_id: switch.YOUR_SENSOR
to: 'on'
for:
minutes: 5
action:
- service: switch.turn_off
entity_id: switch.YOUR_ZIGBEE_CONTROL_OUTLET
SENSOR CALIBRATION
This step is not strictly necessary although recommended specially for consistency in soil moisture measurements when using multiple sensors. By skipping this step a default value is already set in the sketch.
Calibration Steps
After loading the code to the sensor and by making use of the serial monitor, do as follows:
- Leave the sensor on the top of the table without touching it with your hands.
- Take note of the value shown as “SOIL ADC”. We are to call this value SOIL ADC AIR.
- Immerse the sensor in water until the indicated line “put this part to soil”
- Take note of the value shown as “SOIL ADC”. We are to call this value SOIL ADC WATER.
- Now you can replace values
SOIL_ADC_AIR
andSOIL_ADC_WATER
in this same sketch and upload it again.
After uploading the sketch again, but now with updatedSOIL_ADC_AIR
andSOIL_ADC_WATER
, you should see that “SOIL MOIST (%)”, properly shows 100% when the sensor is immerse in water and 0% when it is outside (and dry).
For even a more accurate soil moisture calibration, please google for soil water holding capacity and calibrate accordingly.
Caveat: refer to post #11 for the I2C_AHT10 library necessary for this sketch.
#include <avr/wdt.h>
#include "I2C_AHT10.h"
// SoftwareSerial lorawan_serial(LORAWAN_TX, LORAWAN_RX);
AHT10 aht;
// Sensor Calibration Parameters
#define SOIL_ADC_WATER 583 // Raw read when sensor probe submerged in water. See SOIL_ADC in serial monitor.
#define SOIL_ADC_AIR 888 // Raw read when sensor probe is on air. See SOIL_ADC in serial monitor.
#define SOIL_ADC_UNIT (100.0 / (SOIL_ADC_AIR - SOIL_ADC_WATER))
#define VOLTAGE_PIN A3
#define PWM_OUT_PIN 9
#define SENSOR_POWER_PIN 5
#define ADC_PIN A2
bool readSensorStatus = false;
int soil_adc = 0; // variable to store the value coming from the sensor
int soil_percent = 0;
int bat_adc = 0; // the voltage of battery
int bat_vol = 0;
int tx_count = 0;
int InternalReferenceVoltage = 1101L; // Adjust this value to your boards specific internal BG voltage x1000
float temperature = 0.0;
float humidity = 0.0;
int count = 0; // WDT count
void setup() {
pinMode(SENSOR_POWER_PIN, OUTPUT);
digitalWrite(SENSOR_POWER_PIN, HIGH); // Sensor Power ON
log_init();
log_out("---------------- Start----------------");
pinMode(PWM_OUT_PIN, OUTPUT); // digitalWrite(PWM_OUT_PIN, LOW);
TCCR1A = bit(COM1A0); // toggle OC1A on Compare Match
TCCR1B = bit(WGM12) | bit(CS10); // CTC, scale to clock
OCR1A = 1; // compare A register value (5000 * clock speed / 1024).When OCR1A == 1, PWM is 2MHz
}
void loop() {
log_out("[Loop Start]");
read_sensor();
sensor_log();
log_out("[Loop Over]");
delay(800);
}
// ---------------------- Task -----------------------------
void read_sensor() {
// ----- Read Vcc Voltage -----
// REFS1 REFS0 --> 0 1, AVcc internal ref.
// MUX3 MUX2 MUX1 MUX0 --> Mux configuration:1110 for 1.1V (VBG: Voltage Bandgap)
// Configuring ADMUX
ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);
delay(50); // Wait for stabilization, otherwise first value can be wrong. In principle, the ADMUX does not need to be configure every time but it gets reconfigured to read soil humidity.
// Start a conversion
ADCSRA |= _BV( ADSC );
// Wait for it to complete
while( ( (ADCSRA & (1<<ADSC)) != 0 ) );
// Scale the value
bat_adc = ADC;
bat_vol = (((InternalReferenceVoltage * 1024L) / ADC) + 5L) / 10L;
// ----- Read Soil Moisture -----
// Soil sensor is on ADC2
ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR) | (0<<MUX3) | (0<<MUX2) | (1<<MUX1) | (0<<MUX0);
delay(50); // Wait for stabilization, otherwise first value can be wrong. In principle, the ADMUX does not need to be configure every time but it gets reconfigured to read soil humidity.
// Start a conversion
ADCSRA |= _BV( ADSC );
// Wait for it to complete
while( ( (ADCSRA & (1<<ADSC)) != 0 ) );
// Scale the value
soil_adc = ADC;
soil_percent = (int)((SOIL_ADC_AIR - soil_adc) * SOIL_ADC_UNIT);
if (soil_percent >= 100) {
soil_percent = 100;
}
else if (soil_percent <= 0) {
soil_percent = 0;
}
// ----- Read AHT10 -----
read_AHT10();
}
// switch VCC on (sensors on)
void sensorPowerOn(void) {
digitalWrite(SENSOR_POWER_PIN, HIGH);//Sensor power on
}
// switch VCC off (sensor off)
void sensorPowerOff(void) {
digitalWrite(SENSOR_POWER_PIN, LOW);//Sensor power off
}
bool read_AHT10() {
bool acquired = false;
digitalWrite(SENSOR_POWER_PIN, HIGH); // Power on AHT10
delay(20); // Manufacture notice: Wait 20ms, at most, to enter idle state
if (aht.begin() == false) {
// log_out("AHT10 not detected. Please check wiring. Freezing.");
} else {
int x = 0;
while (x < 10 && !acquired) {
if (aht.available() == true) {
temperature = aht.getTemperature();
humidity = aht.getHumidity();
acquired = true;
// log_out("AHT10 Read Success.");
} else {
delay(50);
x++;
}
}
}
digitalWrite(SENSOR_POWER_PIN, LOW); // Power off ATH10
return acquired;
}
bool aht_init() {
bool ret = false;
Wire.begin();
delay(100);
sensorPowerOff();
delay(100);
sensorPowerOn();
delay(300);
if (aht.begin() == false)
log_out("AHT10 not detected. Please check wiring. Freezing.");
else {
int x=0;
while (x<10) {
if (aht.available() == true) {
temperature = aht.getTemperature();
humidity = aht.getHumidity();
ret = true;
log_out("AHT10 Read Success.");
break;
} else {
delay(1000);
x++;
}
}
}
Wire.end();
return ret;
}
void pwm_init() {
pinMode(PWM_OUT_PIN, OUTPUT); // digitalWrite(PWM_OUT_PIN, LOW);
TCCR1A = bit(COM1A0); // toggle OC1A on Compare Match
TCCR1B = bit(WGM12) | bit(CS10); // CTC, scale to clock
OCR1A = 1; // compare A register value (5000 * clock speed / 1024).When OCR1A == 1, PWM is 2MHz
}
// ---------------------- Log ---------------------------
void log_init() {
Serial.begin(9600);
}
void log_out(const char *log) {
Serial.println(log);
}
void log_out_num(const char *log, int num) {
Serial.print(log);
Serial.println(num);
}
void sensor_log() {
log_out("");
log_out_num("BAT ADC :", bat_adc);
log_out_num("BAT VOL :", bat_vol);
log_out_num("SOIL ADC :", soil_adc);
log_out_num("SOIL MOIST (%):", soil_percent);
log_out_num("TEMPERAUTRE :", (int)temperature);
log_out_num("HUMIDITY :", (int)humidity);
log_out("");
}
THE SENSOR SKETCH
Caveat:
- Refer to post #11 for the I2C_AHT10 library necessary for this sketch.
- If previous sketch was used please beware the TTN payload formatter has been updated!
/*******************************************************************************
* Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
* Copyright (c) 2018 Terry Moore, MCCI
*
* Permission is hereby granted, free of charge, to anyone
* obtaining a copy of this document and accompanying files,
* to do whatever they want with them without any restriction,
* including, but not limited to, copying, modification and redistribution.
* NO WARRANTY OF ANY KIND IS PROVIDED.
*
* This example sends a valid LoRaWAN packet with payload "Hello,
* world!", using frequency and encryption settings matching those of
* the The Things Network.
*
* This uses OTAA (Over-the-air activation), where where a DevEUI and
* application key is configured, which are used in an over-the-air
* activation procedure where a DevAddr and session keys are
* assigned/generated for use with all further communication.
*
* Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in
* g1, 0.1% in g2), but not the TTN fair usage policy (which is probably
* violated by this sketch when left running for longer)!
* To use this sketch, first register your application and device with
* the things network, to set or generate an AppEUI, DevEUI and AppKey.
* Multiple devices can use the same AppEUI, but each device has its own
* DevEUI and AppKey.
*
*
* Do not forget to define the radio type correctly in
* arduino-lmic/project_config/lmic_project_config.h or from your BOARDS.txt.
*
*******************************************************************************/
/*
A3: Battery voltage -> Not used. It is instead read via register configuration amd bandgap reference voltage. See ADMUX
A2: Soil moisture signal -> Read via register configuration. See ADMUX
D5: Control power for AHT10 (and Battery Voltage measurement via A3 although not used)
*/
// MCCI LoRaWAN LMIC library, Version: 4.0.0
#include <lmic.h>
// aht10 library, Date: 03-01-2020
// https://github.com/Makerfabs/Project_IoT-Irrigation-System/tree/master/LoraTransmitterADCAHT10
#include "I2C_AHT10.h"
// Lightweight low power library for Arduino, Version: 1.81, Date: 21-01-2020
#include <LowPower.h>
// standard libraries
#include <hal/hal.h>
# define sketchVersion "Tamadite_20230903"
AHT10 aht;
// This EUI must be in little-endian format, so least-significant-byte
// first. When copying an EUI from ttnctl output, this means to reverse
// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3,
// 0x70.
// APPEUI can be ceros
static const u1_t PROGMEM APPEUI[8]={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);}
// This should also be in little endian format, see above.
static const u1_t PROGMEM DEVEUI[8]={ 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB };
void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);}
// This key should be in big endian format (or, since it is not really a
// number but a block of memory, endianness does not really apply). In
// practice, a key taken from ttnctl can be copied as-is.
static const u1_t PROGMEM APPKEY[16] = { 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB };
void os_getDevKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16);}
// payload to send to TTN gateway
static osjob_t sendjob;
// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 1200; // 1200sec = 20min
// sensors pin mapping
int sensorPin = A2; // select the input pin for the potentiometer
int bat_vol = 0;
#define SENSOR_POWER_PIN 5 // AHT
// Sensor Calibration Parameters
#define SOIL_ADC_WATER 583 // Raw read when sensor probe submerged in water. See SOIL_ADC in serial monitor.
#define SOIL_ADC_AIR 888 // Raw read when sensor probe is on air. See SOIL_ADC in serial monitor.
#define SOIL_ADC_UNIT (100.0 / (SOIL_ADC_AIR - SOIL_ADC_WATER))
#define PWM_OUT_PIN 9
// RFM95 pin mapping
const lmic_pinmap lmic_pins = {
.nss = 10,
.rxtx = LMIC_UNUSED_PIN,
.rst = 4,
.dio = {2, 6, 7},
};
int InternalReferenceVoltage = 1101L; // This value is crucial for accuracy. Refer to instructions on how to get it.
bool readSensorStatus = false;
int soil_adc = 0; // variable to store the value coming from the sensor
int soil_percent = 0;
float temperature = 0.0;
float humidity = 0.0;
void onEvent (ev_t ev) {
/*
EV_SCAN_TIMEOUT: 1
EV_BEACON_FOUND: 2
EV_BEACON_MISSED: 3
EV_BEACON_TRACKED: 4
EV_JOINING: 5
EV_JOINED: 6
EV_JOIN_FAILED: 8
EV_REJOIN_FAILED: 9
EV_TXCOMPLETE: 10
EV_LOST_TSYNC: 11
EV_RESET: 12
EV_RXCOMPLETE: 13
EV_LINK_DEAD: 14
EV_LINK_ALIVE: 15
EV_TXSTART: 17
EV_TXCANCELED: 18
EV_RXSTART: 19
EV_JOIN_TXCOMPLETE: 20
*/
Serial.println(ev);
#if DEBUG_OUT_ENABLE
Serial.print(os_getTime());
Serial.print(": ");
#endif
switch(ev) {
case EV_TXCOMPLETE: //ev: 10
// Serial.println(F("EV_TXCOMPLETE"));
// Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
if (LMIC.txrxFlags & TXRX_ACK)
// Serial.println(F("Received ack"));
// Schedule next transmission
os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
// Use library from https://github.com/rocketscream/Low-Power
for (int i=0; i<int(TX_INTERVAL/8); i++) {
// low power sleep mode
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
}
do_send(&sendjob);
break;
case EV_JOINED: //ev: 6
u4_t netid = 0;
devaddr_t devaddr = 0;
u1_t nwkKey[16];
u1_t artKey[16];
LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
LMIC_setLinkCheckMode(0);
break;
default:
// Serial.print(F("Unknown event: "));
// Serial.println((unsigned) ev);
break;
}
}
void do_send(osjob_t* j) {
uint8_t payload[8]; //payload for TX
// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
// Serial.println(F("OP_TXRXPEND, not sending"));
} else {
read_sensor();
// prepare payload for TX
// place the bytes into the payload
payload[0] = lowByte(soil_percent);
payload[1] = highByte(soil_percent);
// note: this uses the sflt16 datum (https://github.com/mcci-catena/arduino-lmic#sflt16)
// used range for mapping type float to int: -1...+1, -> value/100
uint16_t payloadTemp = 0;
if (temperature != 0)
payloadTemp = LMIC_f2sflt16(temperature/100);
// int -> bytes
// place the bytes into the payload
payload[2] = lowByte(payloadTemp);
payload[3] = highByte(payloadTemp);
// used range for mapping type float to int: -1...+1, -> value/100
uint16_t payloadHumid = 0;
if(humidity !=0)
payloadHumid = LMIC_f2sflt16(humidity/100);
// int -> bytes
// place the bytes into the payload
payload[4] = lowByte(payloadHumid);
payload[5] = highByte(payloadHumid);
// int -> bytes
// byte battLow = lowByte(bat_vol);
// byte battHigh = highByte(bat_vol);
payload[6] = lowByte(bat_vol);
payload[7] = highByte(bat_vol);
// Prepare upstream data transmission at the next possible time.
LMIC_setTxData2(1, payload, sizeof(payload), 0);
// Serial.println(F("Packet queued"));
}
// Next TX is scheduled after TX_COMPLETE event.
}
void read_sensor() {
// ADC initialization
ADCSRA = _BV(ADEN) | _BV(ADPS1) | _BV(ADPS0); // Enable and set prescaler division factor to 8. Division factor between the system clock frequency and the input clock to the ADC
// ----- Read Soil Moisture -----
// Soil sensor is on ADC2
pinMode(PWM_OUT_PIN, OUTPUT); // configuring signal to feed soil sensor
TCCR1A = (1<<COM1A0) | (0<<COM1A1); // toggle OC1A on Compare Match
TCCR1B = bit(WGM12) | bit(CS10); // Set CTC mode (clear timer on compare match) making TOP OCR1A and immediate update. Scale to clock (no prescaling). Freq. to sensor is 2 MHz.
OCR1A = 1;
ADCSRA |= (0<<ADEN);
ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR) | (0<<MUX3) | (0<<MUX2) | (1<<MUX1) | (0<<MUX0);
delay(100); // Wait for stabilization of the soil sensor return signal. Based on oscilloscope, 12 ms is the minimum, 20 ms should be more than enough.
// Start a conversion
ADCSRA |= _BV( ADSC );
// Wait for it to complete
while ( ! (ADCSRA & (1<<ADIF)) );
soil_adc = ADC;
TCCR1A = (1<<COM1A1) | (0<<COM1A0); // Clear OC1A on compare match
digitalWrite(9, LOW); // Secure signal is low
// Scale the value
soil_percent = (int)((SOIL_ADC_AIR - soil_adc) * SOIL_ADC_UNIT);
if (soil_percent >= 100) {
soil_percent = 100;
}
else if (soil_percent <= 0) {
soil_percent = 0;
}
// ----- Read Vcc Voltage -----
// REFS1 REFS0 --> 0 1, Use internal AVcc at AREF
// MUX3 MUX2 MUX1 MUX0 --> Mux configuration:1110 for 1.1V (VBG: Voltage Bandgap)
// Configuring ADMUX
ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);
delay(10); // Wait for stabilization. From the datasheet: after setting the ACBG bit or enabling the ADC, the user must always allow the reference to start up before the output from the analog comparator or ADC is used.
// Start a conversion. As in datasheet, first value has to be discarded; we take the third value. This is only for AVcc at AREF.
for (int i = 0; i < 3; i++) {
ADCSRA |= _BV( ADSC );
// Wait for it to complete
while( ( (ADCSRA & (1<<ADSC)) != 0 ) );
}
// Start conversion
bat_vol = (((InternalReferenceVoltage * 1024L) / ADC) + 5L) / 10L;
// ----- Read AHT10 -----
read_AHT10();
}
bool read_AHT10() {
bool acquired = false;
digitalWrite(SENSOR_POWER_PIN, HIGH); // Power on AHT10
delay(20); // Manufacture notice: Wait 20ms, at most, to enter idle stat
if (aht.begin() == false) {
// Serial.println("AHT10 not detected. Please check wiring. Freezing.");
}
else {
int x = 0;
while (x < 10 && !acquired) {
if (aht.available() == true) {
temperature = aht.getTemperature();
humidity = aht.getHumidity();
acquired = true;
// Serial.println("AHT10 Read Success.");
}
else {
delay(50);
x++;
}
}
}
digitalWrite(SENSOR_POWER_PIN, LOW); // Power off ATH10
return acquired;
}
void setup() {
Serial.begin(9600);
Serial.print(F("Sketch version: "));
Serial.println(sketchVersion);
Wire.begin(); //Join I2C bus
pinMode(SENSOR_POWER_PIN, OUTPUT); // configuring power on/off switching pin
// LMIC init
os_init();
// Reset the MAC state. Session and pending data transfers will be discarded.
LMIC_reset();
LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100);
// Start job (sending automatically starts OTAA too)
do_send(&sendjob);
}
void loop() {
os_runloop_once();
}
LoRaWAN PAYLOAD FORMAT
Custom Javascript formatter
// TTNV3 Payload Formatter Uplink V0.1
function decodeUplink(input) {
if ((input.fPort > 0) && (input.fPort < 223))
{
var decodedTemp = 0;
var decodedHumi = 0;
var decodedBatt = 0;
// separate raw data from payload
var rawSoil = input.bytes[0] + input.bytes[1] * 256;
var rawTemp = input.bytes[2] + input.bytes[3] * 256;
var rawHumi = input.bytes[4] + input.bytes[5] * 256;
var rawBatt = input.bytes[6] + input.bytes[7] * 256;
// decode raw data to values
decodedTemp = sflt162f(rawTemp) * 100; // value calculated to range -1..x..+1 by dividing /100
decodedHumi = sflt162f(rawHumi) * 100; // value calculated to range -1..x..+1 by dividing /100
if (rawBatt !== 0) decodedBatt = rawBatt / 100; // battery voltage is transmitted in mV, recalculate in V
// definition of the decimal places
decodedTemp = decodedTemp.toFixed(2);
decodedHumi = decodedHumi.toFixed(2);
decodedBatt = decodedBatt.toFixed(2);
// return values
return {
data: {
field1: rawSoil,
field2: decodedTemp,
field3: decodedHumi,
field4: decodedBatt
},
warnings: [],
errors: []
};
}
else {
return {
data: {},
warnings: [],
errors: ["Invalid data received"]
};
}
}
function sflt162f(rawSflt16)
{
// rawSflt16 is the 2-byte number decoded from wherever;
// it's in range 0..0xFFFF
// bit 15 is the sign bit
// bits 14..11 are the exponent
// bits 10..0 are the the mantissa. Unlike IEEE format,
// the msb is transmitted; this means that numbers
// might not be normalized, but makes coding for
// underflow easier.
// As with IEEE format, negative zero is possible, so
// we special-case that in hopes that JavaScript will
// also cooperate.
//
// The result is a number in the open interval (-1.0, 1.0);
//
// throw away high bits for repeatability.
rawSflt16 &= 0xFFFF;
// special case minus zero:
if (rawSflt16 == 0x8000)
return -0.0;
// extract the sign.
var sSign = ((rawSflt16 & 0x8000) !== 0) ? -1 : 1;
// extract the exponent
var exp1 = (rawSflt16 >> 11) & 0xF;
// extract the "mantissa" (the fractional part)
var mant1 = (rawSflt16 & 0x7FF) / 2048.0;
// convert back to a floating point number. We hope
// that Math.pow(2, k) is handled efficiently by
// the JS interpreter! If this is time critical code,
// you can replace by a suitable shift and divide.
var f_unscaled = sSign * mant1 * Math.pow(2, exp1 - 15);
return f_unscaled;
}
continues on post#11