Official Losant + Particle Setup Tutorial


#1

Losant has released their Official Particle Setup Tutorial to show you how to quickly get your project setup to send data to Losant for storage and graphing.

Losant has a power backend programming interface to do all kinds of stuff with your data as it comes in.

Their dashboards are more customizable than anything else I have seen offered in this space.

The free account provides a generous 1 million incoming Particle Publishes for up to 10 unique devices which is way more than the other free tiers others provide. I can publish 200 bytes every 15 seconds and still not come close to hitting the free monthly data limits which is nice.

Their simple Particle Publish Integration allows a very easy way to send numbers, floats, and text data by simply separating each variable by the : sign. Then you can break each variable down on the backend of Losant as described in the toutorial. This method is much easier than the JSON formatting that is required for sending data to other platforms I have used.

Give it a shot, you’ll like it :spark:


Particle to Azure integration sending null values
Using WebHooks to Link to Web Services, I thought I had this figured out
Door open counter and time of days open
#2

Thanks for sharing!


#3

@GasGen This may be a better place to continue this discussion.

Here is how I have my setup working.

First the Particle Integration:

Next is the individual device setup page:


And then the workflow settings that separates the incoming webhook data into individual variables that match the variable names you entered on the device setup page where you named each variable and it’s data type either a number, boolean or string.

I have this setup so the variables are separated by a semi-colon : which makes it really easy to separate variables in the Particle Publish event you send out that Losant receives and then breaks down in the function shown below:

Next is the device state node that saves the new incoming data as a new variable:

Once this is setup correctly you should be able to see the incoming data in the Device Log window at the right of the screen where it says received payload.

The webhook my device sends out is just variables with a semi-colon separating each of them.

Let me know if this helps at all.


#4

Thank you so much for all your time Ryan.

So is your webhook name state? or is it newState?

Here is how my data is sent from Particle.

EF is my webhook event name… cf is the json form field in the webhook, and F is the data sent.

Particle.publish("EF", 
                "{\"cf\":\"" + F + 
                "\"}", 60, PRIVATE, NO_ACK); // NOTE Added NO-ACK to save data. 

I have deleted everything in Losant and am going to start from scratch trying some of your examples. I know I have the function payload data not entered correctly after reviewing yours. Along with not entering the device attributes correctly.


#5

The webhook name is: state

Here is what the Particle Publish code looks like.

  snprintf(publishStateString, sizeof(publishStateString), "%.2f:%.2f:%.2f:%u:%.2f:%.2f:%u:%u:%.2f:%u:%.2f:%.2f:%.2f:%.2f:%u:%u:%.2f:%.2f:%.2f:%.2f:%.2f:%.2f:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:",bms.GetVoltage(),bms.GetCurrent(),bms.GetCurrent() * bms.GetVoltage(),bms.RelativeSOC(),(bms.AverageTimeTillEmpty()/60),(bms.RemainingBatteryCapacity() - bms.FullBatteryCapacity()) / (bms.GetCurrent() *1000),bms.RemainingBatteryCapacity(),bms.FullBatteryCapacity(),(bms.GetTemp()* 9 / 5 + 32),bms.CycleCount(),bms.CellVoltage1(),bms.CellVoltage2(),bms.CellVoltage3(),bms.CellVoltage4(),bms.StateOfHealth(),bms.MaxError(),mppt.GetSolarInputVoltage(),mppt.GetSolarInputCurrent(),mppt.GetLTC4015BatVReading(),mppt.GetLTC4015BatIReading(),mppt.GetLTC4015BatIReading() * mppt.GetLTC4015BatVReading(),(((mppt.GetLTC4015DieTemp() -12010)/45.6) *9.0 / 5.0 + 32.0),0,0,0,0,0,0,0,0,ChgFet,DsgFet,ShutdownLoVoltage,DischargingDisabled,ChargingDisabled,SleepMode,CellBalancing,0,ChargeInhibit,ChargeSuspend,ChargeTermination,0,0,0,0,0,CellUnderVoltage,CellOverVoltage,0,0,0,OverloadDuringDischargeLatch,0,ShortCircuitDuringDischargeLatch,OvertemperatureDuringCharge,OvertemperatureDuringDischarge,UndertemperatureDuringCharge,UndertemperatureDuringDischarge,CxChargeTerm,NtcPause,ChargerSuspended,AbsorbCharge,ConstantVoltage,ConstantCurrent,LinLimitActive,VinUvClActiveMPPT,VinHiEnoughToCharge,VinOverVoltageLockout,ThermalShutdown,OKToCharge,ChargerEnabled );
      Particle.publish("State", publishStateString);

@ScruffR Taught me how to use the snprintf formatting to format the data that that the Publish event sends out as variable “publishStateString”.

This is really simple but may or maynot make sense at first glance.


#6

This post explains how to format webhook data using the snprintf function:


#7

WOW. not thats a long one. lol

Yea @ScruffR taught me how to map floating numbers… save me from pulling my hear out by simply adding a decimal.

This is how mine is written.

float flow = map(adc,2139.0, 10614.0, 0.0, 200.0);
String F = String::format("%.1f", flow);
if (Particle.connected() && millis()-lastpub > 60000) {
		lastpub = millis();

Particle.publish("EF", 
                "{\"cf\":\"" + F + 
                "\"}", 60, PRIVATE, NO_ACK); // NOTE Added NO-ACK to save data.

I just added the if particle.connected and No_auk this week after reading some of the other threads here. Saving me a TON of data. That and I had to shorten my webhook name, figured that helped as well.

So with this in mind for the varibles in Losant should I ignore the webhook name and concentrate on the data name “cf”?

Or would you suggest that I rewrite my particle.publish to match more of the setup you and scruff did?


#8

This is the original code that has the other variables commented out as I don’t need them published right now. Trying to maximize data.
This was how I had to write the string to get it into the webhook and over to google sheets.

Particle.publish("EF", 
                "{\"cf\":\"" + F + 
//               "\",\"v\":\"" + String::format("%.1f",fuel.getSoC()) + // BATTERY 
//                "\",\"c\":\"" + String::format("%.1f",fuel.getVCell()) + // CELL State 
//                "\",\"gp\":\"" + t.readLatLon() + // WORKED AND PUBLISHED  
                "\"}", 60, PRIVATE, NO_ACK); // NOTE Added NO-ACK to save data. 

#9

Try this.

#1 Create a Global Variable by placing this line of code above Setup().

This creates the bucket to hold the data we put into the publishStateString bucket. It’s large enough to hold all the data a Webhook can hold in a single Publish Event which is 255 bytes.

char publishStateString[256]; //This is used to hold the device status info we format and Particle Publish to Losant.

#2. Add this new publish code to your sketch.

snprintf(publishStateString, sizeof(publishStateString), "%.2f:",F );
Particle.publish("EF", publishStateString, 60, PRIVATE, NO_ACK);

#3. Make sure this is considered a Number data type in Losant.

#4. Make sure you follow what I did in the Function & Device state notes I show above.

#5. This thread should help you understand how snprintf works for formatting the data your sending to Losant to have the semi-colon in between every variable we send.


#10

I (obviously) second @RWB’s suggestion to use char arrays instead of String.

Just one note about the readability of very long snprintf() lines.

  snprintf(publishStateString, sizeof(publishStateString), "%.2f:%.2f:%.2f:%u:%.2f:%.2f:%u:%u:%.2f:%u:%.2f:%.2f:%.2f:%.2f:%u:%u:%.2f:%.2f:%.2f:%.2f:%.2f:%.2f:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:%u:",bms.GetVoltage(),bms.GetCurrent(),bms.GetCurrent() * bms.GetVoltage(),bms.RelativeSOC(),(bms.AverageTimeTillEmpty()/60),(bms.RemainingBatteryCapacity() - bms.FullBatteryCapacity()) / (bms.GetCurrent() *1000),bms.RemainingBatteryCapacity(),bms.FullBatteryCapacity(),(bms.GetTemp()* 9 / 5 + 32),bms.CycleCount(),bms.CellVoltage1(),bms.CellVoltage2(),bms.CellVoltage3(),bms.CellVoltage4(),bms.StateOfHealth(),bms.MaxError(),mppt.GetSolarInputVoltage(),mppt.GetSolarInputCurrent(),mppt.GetLTC4015BatVReading(),mppt.GetLTC4015BatIReading(),mppt.GetLTC4015BatIReading() * mppt.GetLTC4015BatVReading(),(((mppt.GetLTC4015DieTemp() -12010)/45.6) *9.0 / 5.0 + 32.0),0,0,0,0,0,0,0,0,ChgFet,DsgFet,ShutdownLoVoltage,DischargingDisabled,ChargingDisabled,SleepMode,CellBalancing,0,ChargeInhibit,ChargeSuspend,ChargeTermination,0,0,0,0,0,CellUnderVoltage,CellOverVoltage,0,0,0,OverloadDuringDischargeLatch,0,ShortCircuitDuringDischargeLatch,OvertemperatureDuringCharge,OvertemperatureDuringDischarge,UndertemperatureDuringCharge,UndertemperatureDuringDischarge,CxChargeTerm,NtcPause,ChargerSuspended,AbsorbCharge,ConstantVoltage,ConstantCurrent,LinLimitActive,VinUvClActiveMPPT,VinHiEnoughToCharge,VinOverVoltageLockout,ThermalShutdown,OKToCharge,ChargerEnabled );

I think we can agree that this is rather hard to follow which field is going where
So you could write the same thing like this

  snprintf(publishStateString, sizeof(publishStateString),
           "%.2f:%.2f:%.2f:"
           "%u:%.2f:%.2f:"
           "%u:%u:%.2f:"
           "%u:%.2f:%.2f:"
           "%.2f:%.2f:%u:"
           "%u:%.2f:%.2f:"
           "%.2f:%.2f:%.2f:"
           "%.2f:%u:%u:"
           "%u:%u:%u:"
           "%u:%u:%u:"
           "%u:%u:%u:"
           "%u:%u:%u:"
           "%u:%u:%u:"
           "%u:%u:%u:"
           "%u:%u:%u:"
           "%u:%u:%u:"
           "%u:%u:%u:"
           "%u:%u:%u:"
           "%u:%u:%u:"
           "%u:%u:%u:"
           "%u:%u:%u:"
           "%u:%u:%u:"
           "%u:%u:%u:"
           "%u:%u:"
           , bms.GetVoltage(), bms.GetCurrent(), bms.GetCurrent() * bms.GetVoltage()
           , bms.RelativeSOC(), (bms.AverageTimeTillEmpty()/60),(bms.RemainingBatteryCapacity() - bms.FullBatteryCapacity()) / (bms.GetCurrent() *1000)
           , bms.RemainingBatteryCapacity(), bms.FullBatteryCapacity(), (bms.GetTemp()* 9 / 5 + 32)
           , bms.CycleCount(), bms.CellVoltage1(), bms.CellVoltage2()
           , bms.CellVoltage3(), bms.CellVoltage4(), bms.StateOfHealth()
           , bms.MaxError(), mppt.GetSolarInputVoltage(), mppt.GetSolarInputCurrent()
           , mppt.GetLTC4015BatVReading(), mppt.GetLTC4015BatIReading(), mppt.GetLTC4015BatIReading() * mppt.GetLTC4015BatVReading()
           , (((mppt.GetLTC4015DieTemp() -12010)/45.6) *9.0 / 5.0 + 32.0), 0, 0
           , 0, 0, 0
           , 0, 0, 0
           , ChgFet, DsgFet, ShutdownLoVoltage
           , DischargingDisabled, ChargingDisabled, SleepMode
           , CellBalancing, 0, ChargeInhibit
           , ChargeSuspend, ChargeTermination, 0
           , 0, 0, 0
           , 0, CellUnderVoltage, CellOverVoltage
           , 0, 0, 0
           , OverloadDuringDischargeLatch, 0, ShortCircuitDuringDischargeLatch
           , OvertemperatureDuringCharge, OvertemperatureDuringDischarge, UndertemperatureDuringCharge
           , UndertemperatureDuringDischarge, CxChargeTerm, NtcPause
           , ChargerSuspended, AbsorbCharge, ConstantVoltage
           , ConstantCurrent, LinLimitActive, VinUvClActiveMPPT
           , VinHiEnoughToCharge, VinOverVoltageLockout, ThermalShutdown
           , OKToCharge, ChargerEnabled);

Still a massive statement, but a bit easier to conceptualise IMO.


#11

Yea, it’s a beast to try to follow :smile:

I actually created a spreadsheet to keep track of it all which helped.

I was surprised how much data I was able to pack into a single Publish Event using the semi-colon spacing as the separator.


#12

Thank you @RWB and @ScruffR. I will change the code, flash using CLI, then work on the losant end and let you know if…when, I get stuck. :slight_smile:


#13

Happy Monday @RWB and @ScruffR

Something is not right… or I did something wrong.

The data is showing correctly on my OLED Screen but the data being published is the second value in the MAP.

Here is the code.

Before setup I a have added.

char publishStateString[256]; //This is used to hold the device status info we format and Particle Publish to Losant.

void setup() {
    inputBoard.setAddress(0);

In loop I have the following code.

float flow = map(adc,2139.0, 10614.0, 0.0, 200.0);
String F = String::format("%.1f", flow);
  
if (Particle.connected() && millis()-lastpub > 60000) {
		lastpub = millis();
//New Publish CODE

snprintf(publishStateString, sizeof(publishStateString), "%.2f:",F );
Particle.publish("EF", publishStateString, 60, PRIVATE, NO_ACK);

}

Now I tried it with both %.1f and %.2f just to make sure it was not a conflict in data format but I am only publishing the 10614 value, not the mapped value or even the correct value, regardless of flow. Like I said it shows correctly on my OLED just not in the console.

What the heck did I do wrong here?


#14

I think it has to do with you declaring the F variable a String for the OLED display.

Replace the new publish code with this and see what happens:

snprintf(publishStateString, sizeof(publishStateString), "%.2f:",flow );
Particle.publish("EF", publishStateString, 60, PRIVATE, NO_ACK);

#15

yup that was it.

Thank you. I will start to work on Losant now and follow your original instructions. Will keep you posted.

Thanks again for all your help.

Cheers.
Tom


#16

Good morning,
I finally got things working thanks to you and @ScruffR. I had to take a break due to frustration but came back with a vengeance.

I wanted to note what I had to change for other if they run into a problem. For me having a webhook going to google sheets and the same trigger going to Losant would cause problems. I was unable to graph anything. What I noticed was that Losant was seeing the webhook confirmation back to Particle to come in as data. This was causing havoc on my end.

What I ended up doing was a quick change of the publish name, bypassing the webhook going to google sheets. As soon as I changed the name and redid the Losant triggers to match the new name everything started populating. I am sure there is a way to filter that in losant or turn off the “new row created confirmations” on the webhook, but I did not dig into that yet. I just wanted to get things working first. :slight_smile:

Thank you again for all your help and time with this. It is much appreciated.

Cheers,
Tom


#17

@ScruffR @RWB
I wanted to add what I did to allow for different time posts going to Losant to this thread. I need my flow rate to publish every min. Now to save data I only need Voltage and SOC to publish every hour or so. The code below is every 10 mins, that is just so I could test it on Losant. Does this look like the best way to accomplish this, by creating two versions of “lastpub”? Note for Losant I used “State” so it was easy for everyone to follow the tutorial from @RWB. I will later change this to something with fewer characters to save every bit of data.

unsigned long lastpub = 0;
unsigned long lastpub2 = 0;

// Pub every 60 seconds
if (Particle.connected() && millis()-lastpub > 60000) {
		lastpub = millis();
snprintf(publishStateString, sizeof(publishStateString), "%.1f",flow ); 
Particle.publish("State", publishStateString, 60, PRIVATE, NO_ACK);
}
// Losant all data with 10 min delay. NOTE LASTPUB2 AND 10 MIN DELAY
if (Particle.connected() && millis()-lastpub2 > 600000) {
		lastpub2 = millis();
snprintf(publishStateString, sizeof(publishStateString), "%.1f:%.2f:%.1f",flow, fuel.getVCell(), fuel.getSoC()); // use this for losant format, removed ADC
Particle.publish("State", publishStateString, 60, PRIVATE, NO_ACK); // use this for losant format.
}

Last question. Should I start a new post with how to convert StateString data to JSON or Webform format for a webhook to google sheets? I was not sure if I should keep that here for Losant people that might want a backup of the data if Losant goes down.

Screenshot just for fun.


#18

Actually just doubling the code isn’t the best style.
Better is to only have the differences have their own flow case and keep the common things in one place

unsigned long lastpub = 0;
unsigned long lastpub2 = 0;

  if (Particle.connected()) {
    publishStateString[0] = '\0';         // make sure the string is reset
    if (millis() - lastpub2 > 600000) {   // time for a full report?
      lastpub2 = lastpub = millis();      // reset BOTH timers as the full report also features the short report data
      snprintf(publishStateString         // use this for losant format, removed ADC
              , sizeof(publishStateString)
              , "%.1f:%.2f:%.1f"
              , flow, fuel.getVCell(), fuel.getSoC()); 
    }
    else if (millis() - lastpub > 60000) { // time for an short report
      lastpub = millis();
      snprintf(publishStateString
              , sizeof(publishStateString)
              , "%.1f"
              , flow); 
    }
    if (publishStateString[0])  // if the string was populated send it
      Particle.publish("State", publishStateString, 60, PRIVATE, NO_ACK); 
  }

#19

Aha,
That makes sense. Thank you so much, glad I asked. Whats really surprising is that I actually understand what you wrote. :joy:
Amazing stuff.