B4R Question Inline C String to B4R String

rwblinn

Well-Known Member
Licensed User
Longtime User
Verification question if below is the right way to convert an Inline C string to a B4R string.
The result is OK, but there might be a better way to convert and return a string.

This example is using the ArduinoJson library. The library method parseObject returns a string.
In the example the idx and name are obtained and assigned to global array of bytes.

B4R Globals
B4X:
Sub Process_Globals
    Public idxb() As Byte
    Public nameb() As Byte
...

B4R MQTT Message Arrived
B4X:
Sub Mqtt_MessageArrived (Topic As String, payload() As Byte)
    Dim bc As ByteConverter
    RunNative("jsonparse", bc.StringFromBytes(payload))
    Log("idxb=", bc.StringFromBytes(idxb))
    Log("name=", bc.StringFromBytes(nameb))

Output Message Arrived
idxb=28
name=Windmesser
***
idxb=4
name=RPi Temp
***

B4R Inline C
B4X:
#if C
// Include the ArduinoJson library (see https://github.com/bblanchon/ArduinoJson)
#include <ArduinoJson.h>

// Define the dynamic buffer - set size to 500 as Domoticz Payload can be quite large
DynamicJsonBuffer  jsonBuffer(500);
// String for converting payload to array of bytes
String str;

// Parse the json payload
void jsonparse (B4R::Object* String) {
   // Parse (see arduino parse example)
   const char* Text = (const char*)String->data.PointerField;
   JsonObject& root = jsonBuffer.parseObject(Text);

   // Test if parsing succeeds
   if (!root.success()) {
         Serial.print("Parsing Failed");
       return;
     }

   // Get the idx key, assign to the string str, convert to bytes strb and assign to the B4R global var idxb
   str = root["idx"].asString();
   byte strb[str.length() + 1];
   str.getBytes(strb, sizeof(strb));
   b4r_main::_idxb->data = strb;
   b4r_main::_idxb->length = sizeof(strb);

   // Same as previous but for name
   str = root["name"].asString();
   Serial.println(str);
   byte nameb[str.length() + 1];
   str.getBytes(nameb, sizeof(nameb));
   b4r_main::_nameb->data = nameb;
   b4r_main::_nameb->length = sizeof(nameb);

   // MORE TO ADD ... make a function for it.
}
#End If
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Note that you don't need to convert arrays of bytes to B4R strings in order to print them. If you can, avoid converting arrays of bytes to B4R strings. It just wastes memory.

Your code is problematic.

B4X:
b4r_main::_nameb->data = nameb;
You are making a global variable point to the stack. It will become invalid when the current method ends. It might work in some cases and fail in others.

How complex is the json string? Can you post it?
If it is not too complex then it will be easier to parse it with ByteConverter.
 
Upvote 0

rwblinn

Well-Known Member
Licensed User
Longtime User
Note that you don't need to convert arrays of bytes to B4R strings in order to print them. If you can, avoid converting arrays of bytes to B4R strings. It just wastes memory.
Your code is problematic.
Yes, thought so as well = Thanks for confirming. The JSON string example. The main keys needed are the idx, name and nvalue.:
B4X:
{   
"Battery" : 255,   
"RSSI" : 6,   
"description" : "",   
"dtype" : "Lighting 4",   
"id" : "5C0703",   
"idx" : 58,   
"name" : "FrontDoor",   
"nvalue" : 1,   
"stype" : "PT2262",   
"svalue1" : "512",   
"switchType" : "Contact",   
"unit" : 0}
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
Full example:
B4X:
Private Sub AppStart
   Serial1.Initialize(115200)
   Dim text() As Byte = "{""Battery"" : 255, ""RSSI"" : 6,""description"" : """",   ""dtype"" : ""Lighting 4"",   ""id"" : ""5C0703"",   ""idx"" : 58,   ""name"" : ""FrontDoor"",   ""nvalue"" : 1,   ""stype"" : ""PT2262"",   ""svalue1"" : ""512"",   ""switchType"" : ""Contact"",   ""unit"" : 0}"
   Dim i1 = -1, i2 = -1, i3 = -1, i4 = -1 As Int
   Dim q() As Byte = QUOTE
   Dim colon() As Byte = ":"
   Dim comma() As Byte = ","
   Do While True
       i1 = bc.IndexOf2(text, q, i4 + 1)
       If i1 = -1 Then Exit
       i2 = bc.IndexOf2(text, q, i1 + 1)
       If i2 = -1 Then Exit
       i3 = bc.IndexOf2(text, colon, i2 + 1)
       i4 = bc.IndexOf2(text, comma, i3 + 1)
       If i4 = -1 Then i4 = text.Length - 1
       HandleKeyAndValue(text, i1, i2, i3, i4)
       'Log(StackBufferUsage) no impact on the stack buffer
   Loop
   Log("After")
End Sub

Sub HandleKeyAndValue(text() As Byte, i1 As Int, i2 As Int, i3 As Int, i4 As Int)
   Dim key() As Byte = bc.SubString2(text, i1 + 1, i2)
   Dim value() As Byte = bc.SubString2(text, i3 + 1, i4)
   value = bc.Trim(value)
   If value(0) = 34 Then
       value = bc.SubString2(value, 1, value.Length - 1)
   End If
   Log(key, ": ", value) 'get the values here
End Sub
Note that the memory required is not affected by the text length.
 
Upvote 0

rwblinn

Well-Known Member
Licensed User
Longtime User
Great example = many thanks. Generic solution to parse simple json strings.
Have included in the Contact Monitor Solution in progress:
Domoticz Home Automation sends MQTT messages. An ESP8266 listens to topic "domoticz/out", parses the message and takes action according the device idx and its nvalue. The ESP prototype has several LEDs and Buzzer which are triggered depending idx & nvalue.

EDIT 20171220: Updated based on guidance given in Post #6 (Thanks).

Code Snippet
B4X:
Private bc As ByteConverter

Sub Mqtt_MessageArrived (Topic As String, payload() As Byte)
   ' Get the domoticz idx & value, check and action accordingly
   Dim idx() As Byte = GetJsonKey(payload, "idx")
   Dim nvalue() As Byte = GetJsonKey(payload, "nvalue")
   Select bc.StringFromBytes(idx)
       Case IdxFrontDoor
           Alarm = True
           LEDAlarmPin.DigitalWrite(Alarm)
           AlarmTone
       ' ADD MORE
       Case IdxBackDoor
           ' DEFINE ACTION
   End Select
End Sub

Private Sub GetJsonKey(text() As Byte, SearchKey() As Byte) As Byte() As Byte()
   Dim i1 = -1, i2 = -1, i3 = -1, i4 = -1 As Int
   Dim q() As Byte = QUOTE
   Dim colon() As Byte = ":"
   Dim comma() As Byte = ","
   Do While True
       i1 = bc.IndexOf2(text, q, i4 + 1)
       If i1 = -1 Then Exit
       i2 = bc.IndexOf2(text, q, i1 + 1)
       If i2 = -1 Then Exit
       i3 = bc.IndexOf2(text, colon, i2 + 1)
       i4 = bc.IndexOf2(text, comma, i3 + 1)
       If i4 = -1 Then i4 = text.Length - 1
       Dim key() As Byte = bc.SubString2(text, i1 + 1, i2)
    
       If key = SearchKey Then
           Dim value() As Byte = bc.SubString2(text, i3 + 1, i4)
           value = bc.Trim(value)
           If value(0) = 34 Then
               value = bc.SubString2(value, 1, value.Length - 1)
           End If
           Return value
       End If
   Loop
   Return ""
End Sub
 
Last edited:
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
Your code works properly for your case.

Note that unlike the code I posted, the stack buffer usage will increase in every iteration.
This can be solved by moving the code that creates objects on the stack to a different sub. This way the memory is immediately released.

A simple improvement:
B4X:
Private Sub GetJsonKey(text() As Byte, SearchKey() As Byte) As Byte()
Dim i1 = -1, i2 = -1, i3 = -1, i4 = -1 As Int
    Dim q() As Byte = QUOTE
    Dim colon() As Byte = ":"
    Dim comma() As Byte = ","
    Do While True
        i1 = bc.IndexOf2(text, q, i4 + 1)
        If i1 = -1 Then Exit
        i2 = bc.IndexOf2(text, q, i1 + 1)
        If i2 = -1 Then Exit
        i3 = bc.IndexOf2(text, colon, i2 + 1)
        i4 = bc.IndexOf2(text, comma, i3 + 1)
        If i4 = -1 Then i4 = text.Length - 1
        Dim key() As Byte = bc.SubString2(text, i1 + 1, i2)
      
        If key = SearchKey Then
          Dim value() As Byte = bc.SubString2(text, i3 + 1, i4)
           value = bc.Trim(value)
           If value(0) = 34 Then
              value = bc.SubString2(value, 1, value.Length - 1)
            End If
            Return value
        End If
    Loop
    Return ""
End Sub

1. No conversions between bytes to strings.
2. The value is only extracted when the key is found.
 
Upvote 0
Top