Skip to content
This repository has been archived by the owner on Dec 8, 2023. It is now read-only.

More issues with the Arduino Serial example #74

Closed
djaus2 opened this issue Sep 1, 2021 · 13 comments
Closed

More issues with the Arduino Serial example #74

djaus2 opened this issue Sep 1, 2021 · 13 comments
Assignees
Labels
help wanted Seeking contributions from the community, attention is needed question Further information is requested

Comments

@djaus2
Copy link

djaus2 commented Sep 1, 2021

Following on from the closed issue Getting Arduino Example to work .. Specifying the COM Port #66

I've created a public repository just for the Arduino Serial sample : djaus2/ArduinoSerialPnPExample

I can't get the modeling to work.
The overall aim there is to implement a PnP component that handles the Grove Beginner Kit for Arduino functionality.

Currently:

  • Implements temperature measurement with Grove BME280 Sensor.
  • Implements some properties and commands but can't get these to work from Azure IoT Explorer.

Q What needs to change with the config.json and the Schema files to get this all to work properly when exercised from Azure IoT Explorer?:

  • Actioning commands.
  • Setting propoerties
@djaus2 djaus2 added help wanted Seeking contributions from the community, attention is needed question Further information is requested labels Sep 1, 2021
@djaus2
Copy link
Author

djaus2 commented Sep 3, 2021

Drilling into the Arduino Sample I can see that:
CbSampleRate() is not called at startup in the Arduino code, although I can see the property set reaches PnP_ProcessTwinData()

The json sent for this is ````{"desired":{"sample_rate":9000,"$version":7},"reported":{"$version":1}}```
The app has it set at 1000 and is unchanged.
Nb: The updateState is Complete at this point.

If I do a property set from IoT Explorer the payload is as at PnP_ProcessTwinData is:
{"sample_rate":5000,"$version":8} and no change to sample rate is actioned.
Nb: The updateState is only Partial when this call is made.

So it seems that property updates are not making through the bridge to the Arduino device.

The relevant code in config.json is

                "properties": {
                    "sample_rate": {
                        "name": "sample_rate",
                        "displayName":"Sample Rate",
                        "dataType": "integer",
                        "required": true
                    }
                }

and in the schema:

  "contents": [
    {
      "@type": "Property",
      "displayName": "Sample Rate",
      "description": "Rate of temperature readings.",
      "name": "sample_rate",
      "schema": "integer",
      "writable": true
    }
]

@djaus2
Copy link
Author

djaus2 commented Sep 4, 2021

Current Bridge output:
Nb: Some debug messages have been added in PnP_ProcessTwinData() code.

Info: 
 -- Press Ctrl+C to stop PnpBridge

Info: Using configuration from specified file path: C:\Users\DavidJones\source\repos\ArduinoSerailPnPExample\Config\config.json
Info: Starting Azure PnpBridge
Info: Pnp Bridge is running as am IoT egde device.
Info: Pnp Bridge creation succeeded.
Info: Connection_type is [connection_string]
Info: Tracing is disabled
Info: IoT Edge Device configuration initialized successfully
Info: Building Pnp Bridge Adapter Manager, Adapters & Components
Info: Pnp Adapter with adapter ID serial-pnp-interface has been created.
Info: Pnp Adapter Manager created successfully.
Info: Opening com port COM4
Info: Pnp components created successfully.
Info: Pnp components built in model successfully.
Info: Sent reset request
Info: Receieved reset response
Info: Sent descriptor requestInfo: Connected to Azure IoT Hub

Info: Receieved descriptor response, of length 336
Info: Device Version : 1
Info: Device Name    : Example Thermometer
Info: Interface ID : http://contoso.com/thermometer_example
Info:   Property type : 3
Info:   Name : temperature
Info:   Display Name : Ambient Temperature
Info:   Description : A sample of the ambient temperature.
Info:   Unit : celsius
Info:   Property type : 2
Info:   Name : sample_rate
Info:   Display Name : Sample Rate
Info:   Description : Sample Rate of temperature measurements
Info:   Unit : ms
Info:   Property type : 1
Info:   Name : calibrate
Info:   Display Name : Calibrate Temperature
Info:   Description : Calibrates the thermometer
Info:   Property type : 1
Info:   Name : toggle_state
Info:   Display Name : Toggle State
Info:   Description : Enable-disbale sampling
Info: Pnp components started successfully.
Info: Processing property update for the device or module twin
Info: PnP_ProcessTwinData() updateState: Complete
Info: {"desired":{"sample_rate":9000,"$version":10},"reported":{"$version":1}}
=== Nothing further wrt update here ==
Info: temperature: 17.040001
Info: PnpBridge_PnpBridgeStateTelemetryCallback called, result=0, telemetry=PnpBridge configuration complete
Info: SerialDataSendEventCallback called, result=0, telemetry=temperature
Info: temperature: 17.049999
Info: SerialDataSendEventCallback called, result=0, telemetry=temperature
Info: temperature: 17.059999
Info: SerialDataSendEventCallback called, result=0, telemetry=temperature
Info: temperature: 17.070000
Info: SerialDataSendEventCallback called, result=0, telemetry=temperature
Info: Processing property update for the device or module twin
=== Nothing further wrt update here ==
Info: temperature: 17.080000
Info: SerialDataSendEventCallback called, result=0, telemetry=temperature

@djaus2
Copy link
Author

djaus2 commented Sep 4, 2021

Drilling in further, I can see that the Callback is reached in Bridge code:
In VisitDesiredObject( ) in pnp_protocol.c

pnpPropertyCallback(NULL, name, value, version, userContextCallback);

... but the function in the Arduino is not called. (I have a debug message there that does not get displayed.)

The name of the Callback function in the Arduino code is correct in the LOC above.

Nb: It does return though after that LOC.

@djaus2
Copy link
Author

djaus2 commented Sep 4, 2021

In deeper:
The correct value is being discerned by the Bridge and passed to the Callback LOC above.
Both at start and when a Property update is action by IoT Explorer.

So we have the name, value, version all OK.

Must be something wrong with the userContextCallback,
or the Callback isn't found on device
or isn't actually called back.

Nb: userContextCallback is not NULL.
pnpPropertyCallback is also not NULL.

@djaus2
Copy link
Author

djaus2 commented Sep 4, 2021

The Description request gets to the Arduino device at the start but subsequent property set and conmand packets do not.

@djaus2
Copy link
Author

djaus2 commented Sep 5, 2021

Drilling in deeper to where the Property Update is to be actioned in the Bridge Console app ...

The following call in PnpAdapterManager_RoutePropertyCallback( ) in pnpadapter_manager.c gets called:

   // If the child element is NOT an object OR its not a model the application knows about, this is a property of the model's root component.
   // Invoke the application's passed in callback for it to process this property.
   if (!PnP_ProcessTwinData(updateState, payload, size, (const char**) g_PnpBridge->PnpMgr->ComponentsInModel,
               g_PnpBridge->PnpMgr->NumComponents, PnpAdapterManager_RoutePropertyCallback, userContextCallback))

with PnP_ProcessTwinData( ) in pnp_protocol.c

which then calls VisitDesiredObject( ) in same file.

This then calls pnpPropertyCallback(NULL ,name, value, version, userContextCallback) in pnpadapter_manager.c .which is defined as ...

	static void PnpAdapterManager_RoutePropertyCallback(
    		const char* componentName,
    		const char* propertyName,

... This gets called with componentName as NULL as per the call pnpPropertyCallback(NULL.

So the if branch in:

	if (componentName != NULL && propertyName != NULL)
	{
	}

... doesn't get entered and so the PropertyUpdate doesn't happen!

=========================

Back in VisitDesiredObject(), that was called with a list of components, const char** componentsInModel
This iterates through the chidren in desiredObject so can try getting the componentName thus:

            const char* name = json_object_get_name(desiredObject, i);
            JSON_Value* value = json_object_get_value_at(desiredObject, i);
            const char* component = componentsInModel[i];

... Third line added. So ...

	pnpPropertyCallback(NULL, name, value, version, userContextCallback);

... becomes:

	pnpPropertyCallback(component, name, value, version, userContextCallback);

... The if (componentName != NULL && branch is now entered.
a non null componentHandle is determined so the following IS NOW called:

	componentHandle->processPropertyUpdate(componentHandle, propertyName, propertyValue, version, userContextCallback);

All parameters as well as componentHandle->processPropertyUpdate are non null.

Outcome
The Bridge Console app just exits after a little while. :)

@djaus2
Copy link
Author

djaus2 commented Sep 5, 2021

Questions form the previous post are:

  • is the code pnpPropertyCallback(NULL , correct to use NULL or is my determination of the CompnentName correct?
  • If the former is correct then PnpAdapterManager_RoutePropertyCallback needs modification to handle a NULL componentName.
  • If the latter is correct then why does the console app now fail/exit on the componentHandle->processPropertyUpdate call.

Here the Console output ( a heap of Infos( ) added to show where things are at):

Info: Callback
Info: ==== Entering PnpAdapterManager_RoutePropertyCallback()
Info: ==== componentName:
Info: serialpnp
Info: ==== propertyName:
Info: sample_rate
Info: ==== JSON_value: 56
Info: ==== Version: 39
Info: userContextCallback is NOT NULL
Info: =================
Info: Received PnP property update for component=serialpnp, property=sample_rate
Info: componentHandle is NOT NULL
Info: componentHandle->processPropertyUpdate is NOT NULL
Info: Calling update
PS C:\me\DavidJones\source\repos\iot-plug-and-play-bridge\pnpbridge\cmake\pnpbridge_x86\src\pnpbridge\samples\console\Debug>

@djaus2
Copy link
Author

djaus2 commented Sep 5, 2021

Plugging away at this some more. Re: processPropertyUpdate()

SETTING The Callback Info:

void PnpComponentHandleSetPropertyUpdateCallback (PNPBRIDGE_COMPONENT_HANDLE ComponentHandle,
    PNPBRIDGE_COMPONENT_PROPERTY_CALLBACK PropertyUpdateCallback)
{
    PPNPADAPTER_COMPONENT_TAG componentContextTag = (PPNPADAPTER_COMPONENT_TAG)ComponentHandle;
    componentContextTag->processPropertyUpdate = PropertyUpdateCallback;
}

... gets called from SerialPnp_CreatePnpComponent() in serial_pnp.c

2nd line in:

    PnpComponentHandleSetContext(BridgeComponentHandle, deviceContext);
    PnpComponentHandleSetPropertyUpdateCallback(BridgeComponentHandle, SerialPnp_PropertyUpdateHandler);
    PnpComponentHandleSetCommandCallback(BridgeComponentHandle, SerialPnp_CommandUpdateHandler);

** Bridge actioning a Callback:**

void PnpComponentHandleSetPropertyUpdateCallback (PNPBRIDGE_COMPONENT_HANDLE ComponentHandle,
    PNPBRIDGE_COMPONENT_PROPERTY_CALLBACK PropertyUpdateCallback)
{
    PPNPADAPTER_COMPONENT_TAG componentContextTag = (PPNPADAPTER_COMPONENT_TAG)ComponentHandle;
    componentContextTag->processPropertyUpdate = PropertyUpdateCallback;
}

So want SerialPnp_PropertyUpdateHandler() That's in serial_pnp.c
which calls SerialPnp_PropertyHandler()

FINALLY some Serial Transmit code!

IOTHUB_CLIENT_RESULT SerialPnp_PropertyHandler(
    PSERIAL_DEVICE_CONTEXT serialDevice,
    const char* property,
    char* data)
{
    int nameLength = (int)strlen(property);
    int txlength = SERIALPNP_PACKET_NAME_OFFSET + nameLength + dataLength;
    
  ...

    txPacket[SERIALPNP_PACKET_PACKET_LENGTH_OFFSET] = (byte)(txlength & 0xFF);
    txPacket[SERIALPNP_PACKET_PACKET_LENGTH_OFFSET + 1] = (byte)(txlength >> 8);
    txPacket[SERIALPNP_PACKET_PACKET_TYPE_OFFSET] = SERIALPNP_PACKET_TYPE_PROPERTY_REQUEST;
    // txPacket[3] is reserved
    txPacket[SERIALPNP_PACKET_INTERFACE_NUMBER_OFFSET] = (byte)0;
    txPacket[SERIALPNP_PACKET_NAME_LENGTH_OFFSET] = (byte)nameLength;

    memcpy(txPacket + SERIALPNP_PACKET_NAME_OFFSET, property, nameLength);
    memcpy(txPacket + SERIALPNP_PACKET_NAME_OFFSET + nameLength, inputPayload, dataLength);

    LogInfo("Setting property %s to %s", property, input);

** Followed by, this should send the Property Update back to the device:**

    SerialPnp_TxPacket(serialDevice, txPacket, txlength);

@djaus2
Copy link
Author

djaus2 commented Sep 5, 2021

HEY I FINALLY GOT THE PROPERTY UPDATE TO WORK.
THERE IS SOME FURTHER BUGS THAT I FOUND!
WILL FURTHER DOCUMENT.
😄

@djaus2
Copy link
Author

djaus2 commented Sep 6, 2021

THIS WORKS

\iot-plug-and-play-bridge\pnpbridge\src\pnpbridge\common\pnp_protocol.c

In VisitDesiredObject( )

This call fails with NULL. Need a Component:

pnpPropertyCallback(NULL, name, value, version, userContextCallback);

(1) Line 238 Change NULL to component:

pnpPropertyCallback(component , name, value, version, userContextCallback);

Then get the component from the componentsInModel parameter passed to the function

(2) Insert after line 218
const char* component = componentsInModel[i];

\iot-plug-and-play-bridge\pnpbridge\src\adapters\src\serial_pnp\serial_pnp.c

In Function ``SerialPnp_PropertyUpdateHandler()PropertyValue property is assumed to be a string. So if not a string,json_value_get_string(PropertyValue)``` doesn't get a string,
so function SerialPnp_PropertyHandler( ) doesn't get called.
Modify code to handle other types. Get type from (json_value_get_type( ).
2Do: Could use the component metadata.

(3)Change

const char * PropertyValueString = json_value_get_string(PropertyValue);

To:

    char* PropertyValueString = NULL;
    const char* propertyValueString;
    if (json_value_get_type(PropertyValue) == JSONString)
    {
        propertyValueString = json_value_get_string(PropertyValue);
        if (propertyValueString != NULL)
        {
            PropertyValueString = malloc(strlen(propertyValueString + 1));
            strcpy(PropertyValueString, propertyValueString);
        }
        else
        {
            PropertyValueString = malloc(1);
            PropertyValueString[0] = (char)0;
        }     
    }
    else if (json_value_get_type(PropertyValue) == JSONNumber)
    {
	// If a number, assumes it to be an int.  2Do allow for decimals.
        int iVal = (int) json_value_get_number(PropertyValue);
        PropertyValueString = malloc(20);
        sprintf(PropertyValueString, "%d", iVal);
    }
    else if (json_value_get_type(PropertyValue) == JSONBoolean)
    {
        bool iVal = json_value_get_boolean(PropertyValue);
        PropertyValueString = malloc(5);
        propertyValueString = (iVal == 1) ? "true" : "false";
        strcpy(PropertyValueString, propertyValueString);
    }

@djaus2
Copy link
Author

djaus2 commented Sep 6, 2021

Next: Commands still don't work.

@djaus2
Copy link
Author

djaus2 commented Sep 7, 2021

RAN into similar problem getting Commands to work.

In pnpadapter_manager.c
In PnpAdapterManager_DeviceMethodCallback( )

PnP_ParseCommandName(methodName, (const unsigned char**) (&componentName), &componentNameSize, &pnpCommandName);

... returns a NULL componentName

So componentHandle->processCommand doesn't get called.

Simple Fix

I know my single component name is samplepnp

        if (componentName == NULL)
        {
            // Just insert it. In this case serialpnp
            componentName = "serialpnp";
            componentNameSize = strlen(componentName);
        }    

More detailed solution

Using g_PnpBridge->PnpMgr->ComponentsInModel _but still incomplete

            if (g_PnpBridge != NULL)
                if (g_PnpBridge->PnpMgr != NULL)
                    if (g_PnpBridge->PnpMgr->ComponentsInModel != NULL)
                    {
                        int num = g_PnpBridge->PnpMgr->NumComponents;
                        if (num > 0)
                        {
                            int index = 0;  // ?? Perhaps look for the method in all adapaters.
                            componentName = ((const char**)g_PnpBridge->PnpMgr->ComponentsInModel)[index];
                            componentNameSize = strlen(componentName);
                        }
                    }

** This does work but probably do want to search for the method in all adapters.

@usivagna
Copy link
Contributor

Thank you for your interest in the IoT Plug and Play bridge, unfortunately due to new innovations in Azure IoT and changes in product strategy we have come to the decision to deprecate the IoT Plug and Play bridge. Thank you to the community for your support on this journey. The repo is planned to be archived as a read-only repository on November 30th 2023. Thank you to everyone who contributed bug fixes and new adapters for connecting devices with IoT Plug and Play. You can find the latest IoT innovations at https://techcommunity.microsoft.com/t5/internet-of-things-blog/bg-p/IoTBlog. Please note that any use of archived repositories introduces more risks from a security standpoint and should be done with caution.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
help wanted Seeking contributions from the community, attention is needed question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants