Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OS Target information for shared frameworks #414

Closed
RoySalisbury opened this issue Sep 18, 2018 · 12 comments · Fixed by nanoframework/nanoFramework.Runtime.Native#57
Closed

Comments

@RoySalisbury
Copy link

RoySalisbury commented Sep 18, 2018

If there is already a mechanism in place for this, please let me know. I did not see one, so am proposing this.

If within my own library I want to support multiple target devices (ESP32 and NetDuino) because each might have just a bit different way of doing something, I have no way of knowing what target I am running on.

Following the classes written in the .NET Framework, but modified for the smaller scale, I have implemented the "System.Runtime.InteropServices.RuntimeInformation" class. I removed the OSProcessor and OSArchitecture properties, and added a simpler OSTarget enum.

Each target platform would need to implement 2 native calls:

string NativeOSDescription()
int NativeOSTarget()

The OSTarget would be the following

public enum OSTarget
{
    Unknown = 0,
    ST_STM32F4_DISCOVERY = 1,
    ST_STM32F429I_DISCOVERY = 2,
    ST_NUCLEO64_F091RC = 3,
    ST_NUCLEO144_F746ZG = 4,
    ST_STM32F769I_DISCOVERY = 5,
    MBN_QUAIL = 6,
    NETDUINO3_WIFI = 7,
    ESP32_DEVKITC = 8
}

I just took these values form the target list on the home page for the project. They could actually be anything. But using something that the target is commonly known by would be best.

Then within my shared library I could write (this is just a made up example)

public TimeSpan GetUptime()
{
    switch (RuntimeInformation.OSTarget)
    {
        case OSTarget.NETDUINO3_WIFI:
        case OSTarget.ESP32_DEVKITC:
            var ticks = NativeMethods.GetUptime();
            break;
        default:
            var ticks =  System.Environment.TickCount;
    }
    return TimeSpan.FromTicks(ticks);
}

Anything in the "NativeMethods" would need to be available in the core firmware. However, something like this needs nothing extra from the firmware.

public double FixValue(double value)
{
    switch (RuntimeInformation.OSTarget)
    {
        case OSTarget.NETDUINO3_WIFI:
            // Netduino has things swapped when reading ADC values
            value = SwapHiLow(value);
            break;
        case OSTarget.ESP32_DEVKITC:
            // Need to check precision and recalculate
            if (preceision == 2)
            {
                value /= 2;
            }
            break;
    }

    return value;
}

Now I can deploy a single NuGet package that can support multiple targets.

I have the initial code here if you want to take a look at what it would take.

https://github.com/RoySalisbury/lib-CoreLibrary/tree/develop/source/System/Runtime/InteropServices/RuntimeInformation

Could even add the Processor and Architecture back if you want. So you could have:

Processor: RISC ARM
Archecurre: Cortex-M7F

Or whatever.

@josesimoes
Copy link
Member

All that is pretty much available today. You can find it here.

Having said that, one of the major mantras in nanoFramework is "write once run everywhere".
What this means exactly: a developer shouldn't have to worry about what platform the app running. Why? Because either:

  • The assembly it's being referenced doesn't work for it's target hardware (e.g. nanoFramework.Hardware.Esp32.
  • We (or someone along the way) made a poor job and forgot to implement something.

How this works?

  • On the referenced assembly example: the developer can ignore that it's targeting a wrong platform (or trying to outsmart the tools and referencing it anyways to use a nice feature there). It won't get past the deployment stage because the deploy provider checks if the required assemblies are available in the nano device being deployed. If it's an ESP32 with support for that assemble (version and checksum have to match) it will go smoothly. If it's a STM32 it will complain that that assembly is not available in the platform.
  • On the missing feature example: the assemblies in the images have to be in sync with the managed counterparts, no matter the platform. Even if there is a feature or call that doesn't work or it's not implemented it still has to exist otherwise the assemblies integrity checks will fail. What will happen on this situations when the code calls one of those methods: an exception will be thrown. Usually NotImplementedException or perhaps NotSupportedException.

I understand what you are trying to accomplish here.
On the examples above about the extra processing of the ADC, if that would be the case, all that should be inside the library to provide a truly abstracted interface to the developer using the ADC API, "hiding" the possible intricacies to the developer, thus freeing him from having to worry with those nuisances.

Despite theoretically it won't hurt to add more details (such as CPU, etc) to the SystemInfo info class, that will end up increasing the FLASH and RAM size... which is a concern to keep high on the priority list when deciding to add something.

Am I making sense to you? 😄

@networkfusion
Copy link
Member

I can see the need for targeting the correct io pins etc. it would be incredibly useful to have a "model" field for each type of board so that pins could be mapped correctly without creating a different app for each board

@josesimoes
Copy link
Member

josesimoes commented Sep 18, 2018

@networkfusion I guess you can always abstract that pin mapping... But, ultimately, this will be hardware dependent and you either 1) always use the same pins for that feature 2) end up having to map that somehow...

An example of kind-of-standard is the BLUE push button for STM32 boards. It's connected to PA0 on 100% of the boards (as far as I remember/know). BUT for the LED's that's a totally different matter!! Those bump into differente packaging making the pin name being mapped into different hardware pin (and vice-versa), the fact that a particular pheripheral is taking precedence over a simple GPIO (e.g. SPI1 has to use that pin) so the LED moves to the next now, etc... hard to accomplish if you ask me....

@RoySalisbury
Copy link
Author

All that is pretty much available today. You can find it here.

I guess I am used to using the existing .NET Framwork/UWP classes for the information and did not look at non-framework API's.

Having said that, one of the major mantras in nanoFramework is "write once run everywhere".
What this means exactly: a developer shouldn't have to worry about what platform the app running.

That almost never happens. Even in the .NET Framework that MS has spent YEARS on, you still have cases where app code has to work around subtly differences that are out of the control of the framework vendors,

I understand what you are trying to accomplish here.
On the examples above about the extra processing of the ADC, if that would be the case, all that should be inside the library to provide a truly abstracted interface to the developer using the ADC API, "hiding" the possible intricacies to the developer, thus freeing him from having to worry with those nuisances.

The issue may not be at the low level that the native code can fix. It could be that to correctly use a certain feature in a certain way, you have to do things in a specific order. Ir one board has a 12 bit ADC and another has a 10 bit. Its not up to the board to conform to the lowest setting. But a driver may know how to correct for it.

But, as long as you have the ability already in the firmware someplace, thats all that is needed. I would just suggest to try and map the functionality to existing .NET Framework classes and expand from them.

Roy

@piwi1263
Copy link
Member

Hi all,

why not use the DeviceCode from the Device_Block for this (maybe expand with a couple of vals). This is by Device anyhow. Leaves the question where that enum is populated from. Plus the board name is known and even populated. All I want to say is that most info is already there, duplicating stuff and try to keep it in sync and being dynamic too will be challenging. So I suggest to use the already available basics and expand on it.

@sharmavishnu
Copy link

There are 3 parts to this problem:

  1. How do I know what is my target platform (there are genuine reasons for getting to know this)
  2. How do I write code that can automatically switch to the right context. While this is not a good way to write code (as it will break "write once run everywhere"), it is sometimes perhaps necessary to have target specific code.
  3. How do I write target/device specific code.

The answers, as far as I understand are:

  1. The core platform must populate the SystemInfo class. That should be sufficient. If needed, this class can be extended for nanoFramework to provide additional information about CPU speed, Flash and RAM (maybe, with a standard OEM string format to keep it simple)
  2. Using the attributes of the SystemInfo class, one could get enough information to write device specific code
  3. Target specific code should always be outside of the core implementation. Owners can write wrappers in "community contribution" section and leave it to developers for using it.

Given that we have flash and RAM constraints, it is not a good idea to pack code for multiple different processors within the same assembly. At best, the assembly/library implementor can put checks on which platform the library is designed to run on.

@piwi1263
Copy link
Member

One could implement these and maybe expand it a little bit ...
image
Almost all info is already there on the native side, why not make it avail to the managed side so people can react upon it. There is almost no code to it ... but almost.

@AdrianSoundy
Copy link
Member

I agree with @piwi1263 the best way is to expand on the existing SystemInfo class.

But this method can only be used for nanoframework targets it can't be used for different frameworks as suggested by @RoySalisbury. As soon as you put something in that's is not supported on a particular framework it will no longer compile on all frameworks. You would have to create dummy versions of missing functions to just get it to compile. The only way for different frameworks is to use #if / #else / #endif in your code.

@piwi1263
Copy link
Member

piwi1263 commented Sep 18, 2018

At the moment I'm using below kind of generic code to cover some basics, so it is already avail in a limited way:

        private static void InitGpio()
        {
            int _boardLedPin = -1;
            int _buttonLedPin = -1;
            int _userButtonPin = -1;

            // Detect the board first
            string _oem = SystemInfo.OEMString.ToUpper();

            if (_oem.IndexOf("ST_STM32F4_DISCOVERY") > 0)
            {
                // 407 -> LED = PD12 (Green), PD13 (Orange), PD14 (Red), PD15 (Blue). User Button = PA00
                Console.WriteLine("\nBoard ST_STM32F4_DISCOVERY found, initializing Gpio accordingly.\n");
                _boardLedPin = 3 * 16 + 12;
                _buttonLedPin = 3 * 16 + 15;
                _userButtonPin = 0 * 16 + 00;
            }
            else if (_oem.IndexOf("ST_STM32F429I_DISCOVERY") > 0)
            {
                // 429 -> LED = PG13 (Green), PG14 (Red). User Button = PA00
                Console.WriteLine("\nBoard ST_STM32F429I_DISCOVERY found, initializing Gpio accordingly.\n");
                _boardLedPin = 6 * 16 + 13;
                _buttonLedPin = 6 * 16 + 14;
                _userButtonPin = 0 * 16 + 00;
            }
            else if (_oem.IndexOf("NETDUINO3_WIFI") > 0)
            {
                // 427 -> LED = PA10 (Blue), PE09 (Goport). User Button = PB05
                Console.WriteLine("\nBoard NETDUINO3_WIFI found, initializing Gpio accordingly.\n");
                _boardLedPin = 0 * 16 + 10;
                _buttonLedPin = 4 * 16 + 09;
                _userButtonPin = 1 * 16 + 05;
                _snIsSupported = true;
            }
            else if (_oem.IndexOf("I2M_ELECTRON_NF") > 0)
            {
                // Electron -> LED = PA01 (Green), PA08 (Blue). User Button = Not present
                Console.WriteLine("\nBoard Ingenuity Electron found, initializing Gpio accordingly.\n");
                _boardLedPin = 0 * 16 + 01;
                _buttonLedPin = 0 * 16 + 08;
                _userButtonPin = -1;
            }
            else if (_oem.IndexOf("I2M_OXYGEN_NF") > 0)
            {
                // Oxygen -> LED = PA13 (Blue). User Button = Not present
                Console.WriteLine("\nBoard Ingenuity Oxygen found, initializing Gpio accordingly.\n");
                _boardLedPin = 0 * 16 + 13;
                _buttonLedPin = -1;
                _userButtonPin = -1;
                _snIsSupported = true;
            }
            else if (_oem.IndexOf("ST_NUCLEO144_F412ZG_NF") > 0)
            {
                // 412 -> LED = PB00 (Green), PB07 (Blue), PB14 (Red). User Button = PC13
                Console.WriteLine("\nBoard ST_NUCLEO144_F412ZG_NF found, initializing Gpio accordingly.\n");
                _boardLedPin = 1 * 16 + 00;
                _buttonLedPin = 1 * 16 + 07;
                _userButtonPin = 2 * 16 + 13;
                _snIsSupported = false;
            }
            else if (_oem.IndexOf("ST_STM32F769I_DISCOVERY") > 0)
            {
                // 412 -> LED = PJ05 (Green), PJ13 (Red). User Button = PA00
                Console.WriteLine("\nBoard ST_STM32F769I_DISCOVERY found, initializing Gpio accordingly.\n");
                _boardLedPin = 9 * 16 + 05;
                _buttonLedPin = 9 * 16 + 13;
                _userButtonPin = 0 * 16 + 0;
                _snIsSupported = false;
            }
            else if (_oem.IndexOf("GHI_FEZ_CERBERUS_NF") > 0)
            {
                // 405 -> LED = PC04 (Green). User Button = Not present
                Console.WriteLine("\nBoard GHI_FEZ_CERBERUS_NF found, initializing Gpio accordingly.\n");
                _boardLedPin = 2 * 16 + 04;
                _buttonLedPin = -1;
                _userButtonPin = -1;
                _snIsSupported = false;
            }
            else if (_oem.IndexOf("GHI_FEZ_CERBUINO_NET_NF") > 0)
            {
                // 405 -> LED = PC04 (Green). User Button = Not present
                Console.WriteLine("\nBoard GHI_FEZ_CERBUINO_NET_NF found, initializing Gpio accordingly.\n");
                _boardLedPin = 1 * 16 + 02;
                _buttonLedPin = -1;
                _userButtonPin = -1;
                _snIsSupported = false;
            }

            // If avail setup board LED
            if (_boardLedPin != -1)
            {
                _boardLed = GpioController.GetDefault().OpenPin(_boardLedPin);
                _boardLed.SetDriveMode(GpioPinDriveMode.Output);
                _boardLed.Write(GpioPinValue.Low);
            }

            // If avail setup a buttton controlled LED
            if (_buttonLedPin != -1)
            {
                _buttonLed = GpioController.GetDefault().OpenPin(_buttonLedPin);
                _buttonLed.SetDriveMode(GpioPinDriveMode.Output);
                _buttonLed.Write(GpioPinValue.Low);
            }

            // If avail setup board button
            if (_userButtonPin != -1)
            {
                _userButton = GpioController.GetDefault().OpenPin(_userButtonPin);
                _userButton.SetDriveMode(GpioPinDriveMode.Input);
                _userButton.DebounceTimeout = new TimeSpan(0, 0, 0, 0, 100);
                _userButton.ValueChanged += _userButton_ValueChanged;
            }
        }

We have to convince the ESP32 side to follow the STM side or agree on something....

Or are we looking at the re-birth of the HardwareProvider ?

@josesimoes
Copy link
Member

Being able to differentiate one target from another has always to "start" from the image, at build time.

I very much doubt that we could/should keep an enum with all possible targets. I mean we can force others to "register" their targets on a central registry. Or if this is tosed to the manged side, we'll end up with a even most awkward situation of having different assemblies (nanoFramework.Native possibly) with different enums. This is because we have our reference targets, we have to add the community targets, I would want to add the targets that my company will work on, Roy theirs and so on. Pretty quick this will become unmanageable!! 😓

The various properties that are in the SystemInfo class, which I started to point out and that @piwi1263 detailed seem to be the place for this. It seems to me that there are plenty of properties there to be the placeholders for all this. Most of those are empty today. An OEM is free to use them as it sees fit. Taking a step furhter I even suggest that using string data and parse those in the C# app is the most straightforward approach.

If people think that's important to access detail such CPU type we can look into adding that.
(although it's my beliefe that this is too much detail for what's worth, but this is me...)
As for RAM and flash that's already in the target just not exposed in the managed API. For a simple reason: it seems too much and not really useful what to do it this?
Right now it's reachable only to the debugger because it needs that to manage the flash space for deployment and updates.

@josesimoes josesimoes added the FOR DISCUSSION Open for discussion. Contributes from the community are welcome/expected. label Sep 19, 2018
@RoySalisbury
Copy link
Author

I would suggest, that whatever gets exposed publicly, should be implemented in the classnames of the already existing .NET Framework.

I would take a look at both the "RuntimeInformation" and "Environment" classes. These have a lot of the properties being discussed here and it's where someone would naturally look if already familiar with the .NET Framework. Its not an exact one-to-one match, But some of its there.

@stale
Copy link

stale bot commented Jun 13, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale The issue/PR hasn't seen any activity from the past 60 days. label Jun 13, 2019
@stale stale bot closed this as completed Jun 20, 2019
@josesimoes josesimoes removed the stale The issue/PR hasn't seen any activity from the past 60 days. label Jun 20, 2019
@josesimoes josesimoes added this to the Backlog milestone Jun 20, 2019
@josesimoes josesimoes self-assigned this Jun 20, 2019
@josesimoes josesimoes added Status: Under review Type: Enhancement and removed FOR DISCUSSION Open for discussion. Contributes from the community are welcome/expected. Type: Question labels Jun 20, 2019
@josesimoes josesimoes reopened this Jun 20, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants