makerplane / pyEfis

Electronic Flight Information System in Python
GNU General Public License v2.0
58 stars 31 forks source link

Dynamic buttons that interact with and display data #115

Closed e100 closed 6 months ago

e100 commented 10 months ago

I want to create a new feature and wanted to get some feedback before I get started. This might work to also replace the existing menu.

The idea is to define buttons in the configuration file. Each button, when clicked could set one or more data values to fix gateway. Buttons could display a static text or could monitor a data item and change display based on the value. For example it could monitor a data item and change color based on value or change to defined text based on value. or maybe even become disabled when seeing some values. Buttons could be flagged for what screens they display on and if they hide or not when pressing hide. Each button would have its size and location defined in the config. Since buttons can monitor data and set one or more data values, buttons can interact with one another.

Example Use case: Three buttons AP(Auto Pilot) HH(Heading Hold) FP(Flight Plan) Initially HH and FP are disabled and AP is off. They all display as grey indicating they are off. When AP is pressed it sets a data requesting to enable the auto pilot, button turns yellow The flight computer sets a value indicating it is armed. Now the AP button turns green after seeing that data Now that HH and FP see the auto pilot is armed they become enabled. Pressing HH will set data requesting heading hold to the flight computer, HH turns yellow Flight computer engages heading hold, sends message indicating the mode, HH turns green Pressing HH will send message to auto pilot to disengage and turns yellow. Once AP disengages heading hold it sends data and HH turns to grey indicating it is off.

birkelbach commented 10 months ago

The menu system has been one of the more difficult problems in pyEfis. I was trying to basically duplicate a lot of the functionality built into QActions because sometimes we also want these functions to be in buttons, sometimes in menu selections and sometimes with external buttons or keypresses. I don't remember why we couldn't actually use QActions for all of these things (probably had something to do with interacting with FiXGateway). There needs to be an abstraction layer between the buttons/menus/keyboard and the actual functions so that we are not duplicating code for functions like switching display units. Perhaps the right answer is to simply modify data in FiXGateway and use that change in data to activate the function. This seems like what you are suggesting? One thing for sure is that my way was WAAYY to complicated. What you are suggesting is probably more manageable.

I'd say go for it, but let's keep in in a feature branch for now since it will likely require breaking a lot of the existing code.

e100 commented 10 months ago

@birkelbach

Yes, we would keep track of button states in FIXGateway or simply have buttons react to data in FIXGateway My thought was to implement buttons and one could use the buttons as menu items. Buttons can auto hide/show based on screen so you have have different buttons on each screen in the same location. Buttons can be flagged to hide or show when the actions hide or show take place, or they can be persistent on all screens or just select screens.

I think first we need a solid schema to define the buttons, I think this same schema could work for 'keyboard buttons' too Here is my first go at it, I think the only thing that bothers me is this would be difficult to adapt to a different resolution since each button has an explicit location

menu:
  colors:  <- define colors that cen be referenced by name
    red: FF0000
    grey: 808080
    yellow: FFFF00
    orange: FFA500
    white: FFFFFF
    black: 000000
  buttons <- list of buttons to define
    NAME:  <-Name of the button, a unique logical identifier
      height: height in px
      width: width in px
      x: horizontal location
      Y: vertical location
      db_item:  <- Name of DB item to monitor, must exist in the database
      initial_value: <- Optional, On startup, set this value for db_item
      tol:  <- Optional
        display: What to display on 2nd line of button when tol is reached, only valid if db_item is specified
        fg_color: black
        bg_color: red
      fail: <- Optional, Same as tol but for fail
        display: XXXXX
        fg_color: black
        bg_color: red
      title: <- Optional, Text to display on first line of button
      displayOn: <- Optional, list of screens where this button should appear, on other screens it will be hidden, when omitted shows on all screens
        - SCREEN1
        - SCREEN2
      conditions: <- list of conditions that changes the click action and display of the button, first matching condition is used if multiple match.                   
        - value: <- Optional, a string or number, this condition is activated when db_item equals the value defined
            lte: <- Optional, condition activated if db_item is less than or equal to the value here
            gte: <- Optional, condition activated if db_item is greater than or equall to the value here
            and: <- Optional True|False when True evaluated as lte AND gte, when False lte OR gte
                 NOTE: While value, lte, gte and and are optional one must specify value or one or both of lte and gte
          display: What do display on 2nd line of button, if omitted will display the value of the data item, could be an empty string ""
          bg_color: <- button color when this condition is true
          fg_color: <- button color when this condition is true
          actions: <- when this condition is activated, these actions that will be taken when button is pressed
            set: <- list of DB items and a value to set
              - DB_ITEM1: <- name of DB item to modify
                value: <- Optional value to set
                  increment: <- optional, amount to add to existing value of db_item, can be negative value
                 NOTE: While value and increment are both optional one or the other must be specified
            goto: <- Optional name of screen to switch to
            internal: <- Optional (hide,show,next,back,exit)
          not_if: <- list of conditions that if true prevent a click from performing the action(s), takes precedence over only_if
            - DB_ITEM1 <- check value of this item in the DB
              value: <- Optional, a string or number, this condition is true when db_item equalls the value defined
                lte: <- Optional, condition is true if db_item is less than or equall to the value here
                gte: <- Optional, condition is true if db_item is greater than or equall to the value here
                and: <- Optional True|False when True evaluated as lte AND gte, when False lte OR gte
                   NOTE: While value, lte, gte and and are optional one must specify value or one or both of lte and gte
          only_if: <- list of conditions that when true allow the action to take place
            - DB_ITEM1 <- check value of this item in the DB
              value: <- Optional, a string or number, this condition is true when db_item equalls the value defined
                lte: <- Optional, condition is true if db_item is less than or equall to the value here
                gte: <- Optional, condition is true if db_item is greater than or equall to the value here
                and: <- Optional True|False when True evaluated as lte AND gte, when False lte OR gte
                   NOTE: While value, lte, gte and and are optional one must specify value or one or both of lte and gte
e100 commented 10 months ago

@birkelbach

Is the databinding feature only used to simulate button presses? If so, I believe what I am working on could replace that too.

A config something like this would do the same as the existing data binding for physical buttons:

buttons:
  button1: <- just a unique identifier
    db_item: BTN1
    title: Next
    shortcut: "n"  <- associates keyboard shortcut
    order: 2   <- This is the 2nd button in the menu
    displayOn:
      - SCREEN1
      - SCREEN2
    conditions:
      - value: true
        simulate_click: true <- causes the actions below to happen immediately instead of waiting for a click
        actions:
          set:
            - key: BTN1
              value: false <- set BTN1 bool back to false to be ready for next click
          internal: next <- internal action to move to next screen
      - value: false <- whenever BTN1 is false the action below will happen if you click the button with the mouse or use shortcut
        actions:
          internal: next 
birkelbach commented 10 months ago

The data binding is more of a way to activate actions from external sources. Mainly so that external buttons or encoders can be used and the data fed through FiXGateway. It's not there for simulation, rather for replacement. I'd rather use an encoder with a switch over a touchscreen, so that is the direction that my brain goes when working on these types of things.

e100 commented 10 months ago

@birkelbach

I just submitted a pull request that implements the basic features to make this work. I basically started as a copy of the old menu and modified it until I had what I wanted.

You can set BTN1/2/3/4/5 etc to true and it will be just as if a user pressed the button. Most of the documentation is inline in the buttonmenu section of the main.yaml If you read through it and use fix gateway client to set values you can see how the buttons change.

What we are missing compared the the existing features, might not be complete:

It does not play nice with the old menu, databinding and keybindings but I see no reason that someone could not choose to use the old way or change to the new menubuttons, just don't do both at the same time.

What does work:

Looking forward to your feedback after you give it a try

e100 commented 10 months ago

@birkelbach I see you merged into develop branch.

How should we handle the key binding? In my new button feature I can specify shortcuts, but those could conflict with a keybinding. The options I see:

I think we have some overlap in functionality with the data binding too.

e100 commented 6 months ago

This is implemented: https://github.com/makerplane/pyEfis/blob/master/docs/screenbuilder.md#button