Trame Sigfox (payload)

Bonjour à tous,
Je suis sur un projet de ruche connectée a bas de capteur, une nano et une module de communication Sigfox (BRKWS01).
Les envois directs fonctionnent parfaitement en "println":

#include <SoftwareSerial.h>
SoftwareSerial SigFox(4,5); // RX, TX
void setup() 
{
       SigFox.begin(9600);
}

void loop() 
{
  String test ="AT$SF=1234";
  SigFox.println(test);
  delay(1000000); 
}

Mon problème est que je n'arrive pas à envoyer une trame custom avec l'entête "AT$SF=" dans laquelle je veux une assemble binaire de :

uint8_t id;
int16_t temperature;
uint16_t humidity;
uint16_t poids;
uint16_t in;
uint16_t out;
uint8_t vba;

J'ai essayé dans des chaines de caractères, des structures.... rien à faire ca ne marche pas....
Je suis coincé, comment puis je faire?

Les envoies SigFox sont limités à 12 octets, du coup il faut optimiser. Si par exemple pour l'id je n'ai besoin que de 3 bits, est ce que je peux supprimer les 5 premiers de mon uint8_t dans la trame?

Merci de votre aide, je tourne en rond...
Jérémy

ces éléments font bien 12 octets, pas besoin donc de laisser tomber des bits, sauf si vous avez autre chose à envoyer.

Une struct avec ces éléments fait 12 octets - si vous faites tourner ce code:

struct payload {
  uint8_t id;
  int16_t temperature;
  uint16_t humidity;
  uint16_t poids;
  uint16_t in;
  uint16_t out;
  uint8_t vba;
} data;

void setup() {
  Serial.begin(115200);
  Serial.print("taille de la structure = ");
  Serial.println(sizeof(data));
}

void loop() {}

il vous dira (console à 115200)

</sub> <sub>[color=blue]taille de la structure = 12[/color]</sub> <sub>

Si vous ne voulez pas de structure, faites un tableau de 12 uint8_t (octets) et mettez les bons bits aux bons endroits?

Bonjour,
Merci de votre réponse.
J'ai essayé avec une structure de ce type mais comment je l'envoie avec l'entête "AT$SF=" qui va bien?

Dans ce style là mais ça ne marche pas....

SigFox.write("AT$SF="+data);

pas besoin donc de laisser tomber des bits, sauf si vous avez autre chose à envoyer.

Ça va m’intéresser dans un futur proche de rajouter des infos, je veux bien la méthode.
Merci

En C++ vous ne pouvez pas concatener une chaine constante et un buffer générique. vous pourriez concatener 2 Strings avec un S majuscule (la classe String) mais on vous déconseillera fortement de l'utiliser, préférez les fonctions qui restent proches de la mémoire et du C

Effectuez le write en deux temps, un write de la commande et un write du payload ou alors mettez au début de la structure 5 chars de plus initialisés avec AT$SF= et Le payload sera complet

Pour mettre des bouts d'octets dans des endroits particuliers il suffit d'utiliser les fonctions qui traitent les bits

Mais bon - je répondais à votre question théorique - AT$SF= n'attends pas un buffer binaire mais chaque octet doit être représenté en hexadécimal sur 2 caractères (0xA doit être écrit comme 0A pas juste A) - on passe une chaine en codage ASCII

AT$SF=00112233445566778899aabb

Il faudra donc utiliser un print en HEX de chacun des constituants (en faisant attention de bien mettre le caractère '0' devant les valeurs inférieures à 0xF pour que ce soit sur 2 caractères) une fois que vous avez bâti le payload. Utiliser une union avec la struct ci dessus et un tableau de 12 octets simplifiera cela.

Essayez de regarder ce code:

typedef struct rowPayload_s {
  uint8_t id;
  int16_t temperature;
  uint16_t humidity;
  uint16_t poids; // pas terrible de mélanger français et anglais dans les noms des champs, faites un choix
  uint16_t in; // pas très parlant comme nom de variable
  uint16_t out; // pas très parlant comme nom de variable
  uint8_t vba; // pas très parlant comme nom de variable
};

union payload_u
{
  rowPayload_s data;
  uint8_t rawData[sizeof(rowPayload_s)];
} payload;

void setup() {
  Serial.begin(115200);
  Serial.print(F("taille de la structure = "));
  Serial.print(sizeof(rowPayload_s));
  Serial.println(F(" octets"));
  Serial.print(F("taille de l'union = "));
  Serial.print(sizeof(payload));
  Serial.println(F(" octets\n"));

  // on initialise avec des valeurs reconnnaissables
  payload.data.id = 0x00;
  payload.data.temperature = 0x2211;
  payload.data.humidity = 0x4433;
  payload.data.poids = 0x6655;
  payload.data.in = 0x8877;
  payload.data.out = 0xaa99;
  payload.data.vba = 0xbb;

  Serial.print("AT$SF=");

  // l'architecture étant little endian, l'octet de poids faible
  // des valeurs sur 2 octets est en mémoire avant celui de poids fort
  // une valeur 0x1234 sera représentée en mémoire par
  // un octet à 0x34 suivi d'un octet à 0x12
  // cf https://fr.wikipedia.org/wiki/Endianness#Little_endian
  for (byte i = 0; i < sizeof(payload); i++) {
    if (payload.rawData[i] <= 0xF) Serial.print("0"); // pour bien avoir 2 caractères
    Serial.print(payload.rawData[i], HEX);
  }
  Serial.println();

}

void loop() {}

il affichera dans la console

```
taille de la structure = 12 octets
taille de l'union = 12 octets

AT$SF=00112233445566778899AABB
```

attention à l'ordre d'émission des octets, votre arduino est petit-boutiste (mot de poids faible en tête), si vous recevez cela dans une structure côté serveur qui serait gros-boutistes (mot de poids fort en tête) ça ne fonctionnera pas pour les données sur 16 bits, il faudra inverser des octets à la lecture (ou les émettre dans bon sens)


(source wikipedia)

il est a noter que la norme C ou C++ ne définit pas exactement le comportement d'une structure et d'une union et ne garantit pas que les octets seront agencés dans l'ordre de déclaration de la structure, que le compilateur peut faire des optimisations etc. la seule chose qui est garantie c'est que si vous avez utilisé un membre de l'union pour écrire, vous êtes sûr de pouvoir relire avec ce même membre. Dans la pratique cependant la majorité des compilateurs se comportent comme attendu, et donc vous retrouvez dans le tableau de 12 octets la représentation mémoire de la structure telle que déclarée.

Merci pour toutes les infos, je commence à comprendre.
J'en suis là :

struct payload {
  uint8_t id = 2;
  int16_t temperature=-123;
  uint16_t humidity=523;
  uint16_t poids=1263;
  uint16_t bee_in=2569;
  uint16_t bee_out=6589;
  uint8_t vbat=48;
} ;
#include <SoftwareSerial.h>
SoftwareSerial SigFox(4,5); // RX, TX
 
void setup() {
  payload data; 
  //Serial.begin(9600);
  SigFox.begin(9600);
  SigFox.print("AT$SF=");
  SigFox.print(data.id,BIN);
  //SigFox.print(data.temperature,BIN);
  //SigFox.print(data.humidity,BIN);
  //SigFox.print(data.poids,BIN);
  //SigFox.print(data.bee_in,BIN);
  //SigFox.print(data.bee_out,BIN);
  //SigFox.print(data.vbat,BIN);
  SigFox.println(); 
}
 
void loop() {}

Ce code fonctionne, mais je recois "10" dans le backend SigFOX, alors que je veux "2"...
Si je décommente une autre variable de la structure, ou que j'enlève "BIN", ou que je mets en "HEX" en déclarant 0x02, rien ne marche....

Je ne comprends pas la logique.... >:(

en adaptant directement votre code, ca ne donne rien non plus....

typedef struct rowPayload_s {
  uint8_t id;
  int16_t temperature;
  uint16_t humidity;
  uint16_t poids; // pas terrible de mélanger français et anglais dans les noms des champs, faites un choix
  uint16_t in; // pas très parlant comme nom de variable
  uint16_t out; // pas très parlant comme nom de variable
  uint8_t vba; // pas très parlant comme nom de variable
};

union payload_u
{
  rowPayload_s data;
  uint8_t rawData[sizeof(rowPayload_s)];
} payload;

#include <SoftwareSerial.h>
SoftwareSerial SigFox(4,5); // RX, TX

void setup() {

  SigFox.begin(9600);

  // on initialise avec des valeurs reconnnaissables
  payload.data.id = 0x00;
  payload.data.temperature = 0x2211;
  payload.data.humidity = 0x4433;
  payload.data.poids = 0x6655;
  payload.data.in = 0x8877;
  payload.data.out = 0xaa99;
  payload.data.vba = 0xbb;

  SigFox.print("AT$SF=");

  for (byte i = 0; i < sizeof(payload); i++) {
    if (payload.rawData[i] <= 0xF) SigFox.print("0"); // pour bien avoir 2 caractères
    SigFox.print(payload.rawData[i], HEX);
  }
  SigFox.println();

}

void loop() {}

vous avez quoi comme arduino?

nano v3.1

comment vous alimentez le sigFox? vous pouvez poster un schéma et définir les composants que vous avez?

ah oui aussi si je me souviens bien il faut envoyer un '\r' et pas un "\r\n" à la fin de la trame - donc faites un sigfox.write("\r"); et pas de SigFox.println();

avec votre code

  uint8_t id = 2;
...
  SigFox.print("AT$SF=");
  SigFox.print(data.id,BIN);

vous envoyez bien en ASCII sur le port série AT$SF= mais quand vous indiquez BIN au print vous demandez de mettre la valeur en binaire... comme votre ID vaut 2 en binaire ça s'écrit b000000[color=red]10[/color] et la fonction print n'affiche pas les premiers zéro donc va envoyer juste le [color=red]10[/color] de la fin. En tout votre commande c'est AT$SF=10\r\n et Donc c'est bien normal que vous receviez 10 de l'autre côté.

Comment voyez vous le message reçu ?

Ah - il se peut que votre module attende les data avec un espace entre chaque champs

si vous faites un

sigfox.write("AT$SF=00 11 22 33 44 55 66 77 88 99 aa\r");

que voyez vous ?

On touche au but ! ca marche :

typedef struct rowPayload_s {
  uint8_t id;
  int16_t temperature;
  uint16_t humidity;
  uint16_t poids; // pas terrible de mélanger français et anglais dans les noms des champs, faites un choix
  uint16_t in; // pas très parlant comme nom de variable
  uint16_t out; // pas très parlant comme nom de variable
  uint8_t vba; // pas très parlant comme nom de variable
};

union payload_u
{
  rowPayload_s data;
  uint8_t rawData[sizeof(rowPayload_s)];
} payload;

#include <SoftwareSerial.h>
SoftwareSerial SigFox(4,5); // RX, TX

void setup() {

  SigFox.begin(9600);

  // on initialise avec des valeurs reconnnaissables
  payload.data.id = 2;
  payload.data.temperature = -123;
  payload.data.humidity = 523;
  payload.data.poids = 2563;
  payload.data.in = 5698;
  payload.data.out = 3562;
  payload.data.vba = 48;

  SigFox.print("AT$SF=");

  for (byte i = 0; i < sizeof(payload); i++) {
    if (payload.rawData[i] <= 0xF) SigFox.print("0"); // pour bien avoir 2 caractères
    SigFox.print(payload.rawData[i], HEX);
  }
  SigFox.print("\r");

}

void loop() {}

backend avec la déclaration suivante :

ID::uint:8:little-endian Temp::int:16:little-endian Hum::uint:16:little-endian Poids::uint:16:little-endian IN::uint:16:little-endian OUT::uint:16:little-endian Vbat::uint:8:little-endian
0285ff0b02030a4216ea0d30 
ID: 0 
Temp: -123 
Hum: 523 
Poids: 2563 
IN: 5698 
OUT: 3562 
Vbat: 0

a part "id" et "vba"...

essayez avec des espaces?

Avec les espaces le backend ne reçoit que "00"
Cela marche sans les espaces.

le soucis pour l'id et vba doit plutot venir du backend, vu que le payload est ok

OK

l'affichage de "0285ff0b02030a4216ea0d30" c'était côté serveur, c'est ça?

--> à creuser parce que effectivement en Hexa les valeurs en rouges sont bien 2 et 48 en décimal

au fait votre serveur c'est chez sigfox ou c'est déjà routé ailleurs?

Oui, je suis sur le serveur Sigfox avec la config suivante:

ID::uint:8:little-endian Temp::int:16:little-endian Hum::uint:16:little-endian Poids::uint:16:little-endian IN::uint:16:little-endian OUT::uint:16:little-endian Vbat::uint:8:little-endian

en supprimant les "little-endian" des "uint:8" ca fonctionne...

Merci beaucoup pour votre aide !
Je ferai un post de recap du projet quand ca tournera.

Ok oui little Indian ça a du sens au niveau des octets qu'à partir de 2 octets (sinon on parle de l'arrangement des bits mais ça ne se pratique pas)

J'attends mon MKRFOX1200 pour jouer un peu plus avec de façon intégrée