:lang: en
:toc:

[[cha:gstat]]
= GStat Python Module

== Intro

GStat is a Python class used to send messages from LinuxCNC to other Python programs.
It uses GObject to deliver messages, making it easy to listen for specific information.
This is referred to as event-driven programming, which is more efficient then every program polling LinuxCNC at the same time.
GladeVCP, Gscreen, Gmoccapy and QtVCP use GStat extensively.
GStat is in the `hal_glib` module.

.Overview
* First, a program imports the `hal_glib` module and instantiates GStat.
* Then it 'connects' to the messages it wishes to monitor.
* GStat checks LinuxCNC's status every 100 ms and if there are differences from
  the last check, it will send a callback message to all the connected programs
  with the current status.
* When GStat calls the registered function, it sends the GStat object plus any return codes from the message.

Typical code signatures:

[source,python]
----
GSTAT.connect('MESSGAE-TO-LISTEN-FOR', FUNCTION_TO_CALL)

def FUNCTION_TO_CALL(gstat_object, return_codes):
----

Often LAMBDA is used to strip the GSTAT object and manipulate the return codes:

[source,python]
----
GSTAT.connect('MESSGAE-TO-LISTEN-FOR', lambda o, return: FUNCTION_TO_CALL(not return))

def FUNCTION_TO_CALL(return_codes):
----

== Sample GStat Code

There are some basic patterns for using GStat,
depending on what library you are using them in.
If using GStat with GladeVCP, Gscreen, or QtVCP, the GObject library is not needed as
those toolkits already set up GObject.

=== Sample HAL component code pattern

This program creates two HAL pins that output the status of G20/G21.

[source,python]
----
#!/usr/bin/env python3

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import GObject
from gi.repository import GLib
import hal
from hal_glib import GStat
GSTAT = GStat()

# callback to change HAL pin state
def mode_changed(obj, data):
        h['g20'] = not data
        h['g21'] = data

# Make a component and pins
h = hal.component("metric_status")
h.newpin("g20", hal.HAL_BIT, hal.HAL_OUT)
h.newpin("g21", hal.HAL_BIT, hal.HAL_OUT)
h.ready()

# connect a GSTAT message to a callback function
GSTAT.connect("metric-mode-changed",mode_changed)

# force GSTAT to initialize states
GSTAT.forced_update()

# loop till exit
try:
    GLib.MainLoop().run()
except KeyboardInterrupt:
    raise SystemExit
----

This would be loaded with `loadusr python PATH-TO-FILE/FILENAME.py` or if you need to wait for the pins to be made before continuing: +
`loadusr python -Wn metric_status PATH-TO-FILE/FILENAME.py` +
The pins would be: `metric_status.g20` and `metric_status.g21`.

=== GladeVCP Python extension code pattern

This file assumes there are three GTK labels named:

* 'state_label'
* 'e_state_label'
* 'interp_state_label'

[source,python]
----
#!/usr/bin/env python3

from hal_glib import GStat
GSTAT = GStat()

class HandlerClass:

    def __init__(self, halcomp, builder, useropts):
        self.builder = builder

        GSTAT.connect("state-estop",lambda w: self.update_estate_label('ESTOP'))
        GSTAT.connect("state-estop-reset",lambda w: self.update_estate_label('RESET'))

        GSTAT.connect("state-on",lambda w: self.update_state_label('MACHINE ON'))
        GSTAT.connect("state-off",lambda w: self.update_state_label('MACHINE OFF'))

        GSTAT.connect("interp-paused",lambda w: self.update_interp_label('Paused'))
        GSTAT.connect("interp-run",lambda w: self.update_interp_label('Run'))
        GSTAT.connect("interp-idle",lambda w: self.update_interp_label('Idle'))

    def update_state_label(self,text):
        self.builder.get_object('state_label').set_label("State: %s" % (text))

    def update_estate_label(self,text):
        self.builder.get_object('e_state_label').set_label("E State: %s" % (text))

    def update_interp_label(self,text):
        self.builder.get_object('interp_state_label').set_label("Interpreter State: %s" % (text))

def get_handlers(halcomp,builder,useropts):
    return [HandlerClass(halcomp,builder,useropts)]
----

=== QtVCP Python extension code pattern

QtVCP extends GStat, so must be loaded differently but all the messages are available in QtVCP. +
This handler file assumes there are three QLabels named:

* 'state_label'
* 'e_state_label'
* 'interp_state_label'

[source,python]
----
#!/usr/bin/env python3

from qtvcp.core import Status
GSTAT = Status()

class HandlerClass:

    def __init__(self, halcomp,widgets,paths):
        self.w = widgets

        GSTAT.connect("state-estop",lambda w: self.update_estate_label('ESTOP'))
        GSTAT.connect("state-estop-reset",lambda w: self.update_estate_label('RESET'))

        GSTAT.connect("state-on",lambda w: self.update_state_label('MACHINE ON'))
        GSTAT.connect("state-off",lambda w: self.update_state_label('MACHINE OFF'))

        GSTAT.connect("interp-paused",lambda w: self.update_interp_label('Paused'))
        GSTAT.connect("interp-run",lambda w: self.update_interp_label('Run'))
        GSTAT.connect("interp-idle",lambda w: self.update_interp_label('Idle'))

    def update_state_label(self,text):
        self.w.state_label.setText("State: %s" % (text))

    def update_estate_label(self,text):
        self.w.e_state_label.setText("E State: %s" % (text))

    def update_interp_label(self,text):
        self.winterp_state_label.setText("Interpreter State: %s" % (text))

def get_handlers(halcomp,builder,useropts):
    return [HandlerClass(halcomp,widgets,paths)]
----

==  Messages

*periodic* :: '(returns nothing)' -
sent every 100 ms.

*state-estop* :: '(returns nothing)' -
Sent when LinuxCNC is goes into estop.

*state-estop-reset*:: '(returns nothing)' -
Sent when LinuxCNC comes out of estop.

*state-on* :: '(returns nothing)' -
Sent when LinuxCNC is in machine on state.

*state-off*:: '(returns nothing)' -
Sent when LinuxCNC is in machine off state.

*homed* :: '(returns string)' -
Sent as each joint is homed.

*all-homed* :: '(returns nothing)' -
Sent when all defined joints are homed.

*not-all-homed* :: '(returns string)' -
Sends a list of joints not currently homed.

*override_limits_changed* :: '(returns string)' -
Sent if LinuxCNC has been directed to override its limits.

*hard-limits-tripped* :: '(returns bool, Python List)' -
Sent when any hard limit is tripped.
bool indicates if any limit is tripped, the list shows all available joint's current limit values.

*mode-manual* :: '(returns nothing)' -
Sent when LinuxCNC switches to manual mode.

*mode-mdi* :: '(returns nothing)' -
Sent when LinuxCNC switches to MDI mode.

*mode-auto* :: '(returns nothing)' -
Sent when LinuxCNC switches to auto mode.

*command-running* :: '(returns nothing)' -
Sent when running a program or MDI

*command-stopped* :: '(returns nothing)' -
Sent when a program or MDI stopped

*command-error* :: '(returns nothing)' -
Sent when there is a command error

*interp-run* :: '(returns nothing)' -
Sent when LinuxCNC's interpreter is running an MDI or program.

*interp-idle* :: '(returns nothing)' -
Sent when LinuxCNC's interpreter is idle.

*interp-paused* :: '(returns nothing)' -
Sent when LinuxCNC's interpreter is paused.

*interp-reading* :: '(returns nothing)' -
Sent when LinuxCNC's interpreter is reading.

*interp-waiting* :: '(returns nothing)' -
Sent when LinuxCNC's interpreter is waiting.

*jograte-changed* :: '(returns float)' -
Sent when jog rate has changed. +
LinuxCNC does not have an internal jog rate. +
This is GStat's internal jog rate. +
It is expected to be in the machine's native units regardless of the current unit mode .

*jograte-angular-changed* :: '(returns float)' -
Sent when the angular jog rate has changed. +
LinuxCNC does not have an internal angular jog rate. +
This is GStat's internal jog rate. +
It is expected to be in the machine's native units regardless of the current unit mode .

*jogincrement-changed* :: '(returns float, text)' -
Sent when jog increment has changed. +
LinuxCNC does not have an internal jog increment. +
This is GStat's internal jog increment. +
It is expected to be in the machine's native units regardless of the current unit mode .

*jogincrement-angular-changed* :: '(returns float, text)' -
Sent when angular jog increment has changed. +
LinuxCNC does not have an internal angular jog increment. +
This is GStat's internal angular jog increment. +
It is expected to be in the machine's native units regardless of the current unit mode .

*program-pause-changed* :: '(returns bool)' -
Sent when program is paused/unpaused.

*optional-stop-changed* :: '(returns bool)' -
Sent when optional stop is set/unset

*block-delete-changed* :: '(returns bool)' -
sent when block delete is set/unset.

*file-loaded* :: '(returns string)' -
Sent when LinuxCNC has loaded a file

*reload-display* :: '(returns nothing)' -
Sent when there is a request to reload the display

*line-changed* :: '(returns integer)' -
Sent when LinuxCNC has read a new line. +
LinuxCNC does not update this for every type of line.

*tool-in-spindle-changed* :: '(returns integer)' -
Sent when the tool has changed.

*tool-info-changed* :: '(returns Python object)' -
Sent when current tool info changes.

*current-tool-offset* :: '(returns Python object)' -
Sent when the current tool offsets change.

*motion-mode-changed* :: '(returns integer)' -
Sent when motion's mode has changed

*spindle-control-changed* :: '(returns integer, bool, integer, bool)' -
(spindle num, spindle on state, requested spindle direction & rate, at-speed state) +
Sent when spindle direction or running status changes or at-speed changes.

*current-feed-rate* :: '(returns float)' -
Sent when the current feed rate changes.

*current-x-rel-position* :: '(returns float)' -
Sent every 100 ms.

*current-position* :: '(returns pyobject, pyobject, pyobject, pyobject)' -
Sent every 100 ms. +
Returns tuples of position, relative position, distance-to-go and the joint actual position.
Before homing, on multi-joint axes, only joint position is valid.

*current-z-rotation* :: '(returns float)' -
Sent as the current rotatated angle around the Z axis changes

*requested-spindle-speed-changed* :: '(returns float)' -
Sent when the current requested RPM changes

*actual-spindle-speed-changed* :: '(returns float)' -
Sent when the actual RPM changes based on the HAL pin `spindle.0.speed-in`.

*spindle-override-changed* :: '(returns float)' -
Sent when the spindle override value changes +
in percent

*feed-override-changed* :: '(returns float)' -
Sent when the feed override value changes +
in percent

*rapid-override-changed* :: '(returns float)' -
Sent when the rapid override value changes +
in percent (0-100)

*max-velocity-override-changed* :: '(returns float)' -
Sent when the maximum velocity override value changes +
in units per minute

*feed-hold-enabled-changed* :: '(returns bool)' -
Sent when feed hold status changes

*itime-mode* :: '(returns bool)' -
Sent when G93 status changes +
(inverse time mode)

*fpm-mode* :: '(returns bool)' -
Sent when G94 status changes +
(feed per minute mode)

*fpr-mode* :: '(returns bool)' -
Sent when G95 status changes +
(feed per revolution mode)

*css-mode* :: '(returns bool)' -
Sent when G96 status changes +
(constant surface feed mode)

*rpm-mode* :: '(returns bool)' -
Sent when G97 status changes +
(constant RPM mode)

*radius-mode* :: '(returns bool)' -
Sent when G8 status changes +
display X in radius mode

*diameter-mode* :: '(returns bool)' -
Sent when G7 status changes +
display X in Diameter mode

*flood-changed* :: '(returns bool)' -
Sent when flood coolant state changes.

*mist-changed* :: '(returns bool )' -
Sent when mist coolant state changes.

*m-code-changed* :: '(returns string)' -
Sent when active M-codes change

*g-code-changed* :: '(returns string)' -
Sent when active G-code change

*metric-mode-changed* :: '(returns bool)' -
Sent when G21 status changes

*user-system-changed* :: '(returns string)' -
Sent when the reference coordinate system (G5x) changes

*mdi-line-selected* :: '(returns string, string)' -
intended to be sent when an MDI line is selected by user. +
This depends on the widget/libraries used.

*gcode-line-selected* :: '(returns integer)' -
intended to be sent when a G-code line is selected by user. +
This depends on the widget/libraries used.

*graphics-line-selected* :: '(returns integer)' -
intended to be sent when graphics line is selected by user. +
This depends on the widget/libraries used.

*graphics-loading-progress* :: '(returns integer)' -
intended to return percentage done of loading a program or running a program. +
This depends on the widget/libraries used.

*graphics-gcode-error* :: '(returns string)' -
intended to be sent when a G-code error is found when loading. +
This depends on the widget/libraries used.

*graphics-gcode-properties* :: '(returns Python dict)' -
Sent when G-code is loaded. +
The dict contains the following keys:
- `name` (_string_): Name of the loaded file
- `size` (_string_): Size in bytes and lines
- `g0` (_string_): Total rapid distance
- `g1` (_string_): Total feed distance
- `run` (_string_): Estimated program run time
- `toollist` (_list_): List of used tools
- `x` (_string_): X extents (bounds) ^1^
- `x_zero_rxy` (_string_): X extents without rotation around z (bounds) ^1^
- `y` (_string_): Y extents (bounds) ^1^
- `y_zero_rxy` (_string_): Y extents without rotation around z (bounds) ^1^
- `z` (_string_): Z extents (bounds) ^1^
- `z_zero_rxy` (_string_): Z extents without rotation around z (bounds) ^1^
- `machine_unit_sys` (_string_): Machine units ('Metric' or 'Imperial')
- `gcode_units` (_string_): Units in G-code file ('mm' or 'in')
+
[NOTE]
====
1. See the images image:images/gremlin_extents_non-rotated_link.png[align="left",link="images/gremlin_extents_non-rotated.png",alt="extends non-rotated"] and image:images/gremlin_extents_rotated_30_link.png[align="left",link="images/gremlin_extents_rotated_30.png",alt="extends rotated 30"] for a better understanding.
====

*graphics-view-changed* :: '(returns string, Python dict or None)' -
intended to be sent when graphics view is changed. +
This depends on the widget/libraries used.

*mdi-history-changed* :: '(returns None)' -
intended to be sent when an MDI history needs to be reloaded. +
This depends on the widget/libraries used.

*machine-log-changed* :: '(returns None)' -
intended to be sent when machine log has changed. +
This depends on the widget/libraries used.

*update-machine-log* :: '(returns string, string)' -
intended to be sent when updating the machine. +
This depends on the widget/libraries used.

*move-text-lineup* :: '(returns None)' -
intended to be sent when moving the cursor one line up in G-code display. +
This depends on the widget/libraries used.

*move-text-linedown* :: '(returns None)' -
intended to be sent when moving the cursor one line down in G-code display. +
This depends on the widget/libraries used.

*dialog-request* :: '(returns Python dict)' -
intended to be sent when requesting a GUI dialog. +
It uses a Python dict for communication.
The dict must include the following keyname pair: +
* NAME: 'requested dialog name' +
  The dict usually has several keyname pairs - it depends on the dialog. +
  dialogs return information using a general message +
  This depends on the widget/libraries used.

*focus-overlay-changed* :: '(returns bool, string, Python object)' -
intended to be sent when requesting an overlay to be put over the display. +
This depends on the widget/libraries used.

*play-sound* :: '(returns string)' -
intended to be sent when requesting a specific sound file to be played. +
This depends on the widget/libraries used.

*virtual-keyboard* :: '(returns string)' -
intended to be sent when requesting a on screen keyboard. +
This depends on the widget/libraries used.

*dro-reference-change-request* :: '(returns integer)' -
intended to be sent when requesting a DRO widget to change its reference. +
0 = machine, 1 = relative, 3 = distance-to-go +
This depends on the widget/libraries used.

*show-preferences* :: '(returns None)' -
intended to be sent when requesting the screen preferences to be displayed. +
This depends on the widget/libraries used.

*shutdown* :: '(returns None)' -
intended to be sent when requesting LinuxCNC to shutdown. +
This depends on the widget/libraries used.

*error* :: '(returns integer, string)' -
intended to be sent when an error has been reported . +
integer represents the kind of error. ERROR, TEXT or DISPLAY +
string is the actual error message. +
This depends on the widget/libraries used.

*general* :: '(returns Python dict)' -
intended to be sent when message must be sent that is not covered by a more specific message. +
General message should be used a sparsely as reasonable because all object connected to it will have to parse it. +
It uses a Python dict for communication. +
The dict should include and be checked for a unique id  keyname pair: +
* ID: 'UNIQUE_ID_CODE' +
  The dict usually has more keyname pair - it depends on implementation.

*forced-update* :: '(returns None)' -
intended to be sent when one wishes to initialize or arbitrarily update an object. +
This depends on the widget/libraries used.

*progress* :: '(returns integer, Python object)' -
intended to be sent to indicate the progress of a filter program. +
This depends on the widget/libraries used.

*following-error* :: '(returns Python list)' -
returns a list of all joints current following error.

== Functions

These are convenience functions that are commonly used in programming.

*set_jograte* :: '(float)' -
LinuxCNC has no internal concept of jog rate -each GUI has its own.
This is not always convenient. +
This function allows one to set a jog rate for all objects connected to the signal `jograte-changed`. +
It defaults to 15. +
GSTAT.set_jog_rate(10) would set the jog rate to 10 machine-units-per-minute and emit the `jograte-changed` signal.

*get_jograte()* :: '(Nothing)' -
x = GSTAT.get_jograte() would return GSTAT's current internal jograte (float).

*set_jograte_angular* :: '(float)' -

*get_jograte_angular* :: '(None)' -

*set_jog_increment_angular* :: '(float, string)' -

*get_jog_increment_angular* :: '(None)' -

*set_jog_increments* :: '(float, string)' -

*get_jog_increments* :: '(None)' -

*is_all_homed* :: '(nothing)' -
This will return the current state of all_homed (BOOL).

*machine_is_on* :: '(nothing)' -
This will return the current state of machine (BOOL).

*estop_is_clear* :: '(nothing)' -
This will return the state of Estop (BOOL)

*set_tool_touchoff* :: '(tool,axis,value)' -
This command will
. record the current mode,
. switch to MDI mode,
. invoke the MDI command: G10 L10 P[TOOL] [AXIS] [VALUE],
. wait for it to complete,
. invoke G43,
. wait for it to complete,
. switch back to the original mode.

*set_axis_origin* :: '(axis,value)' -
This command will
. record the current mode,
. switch to MDI mode,
. invoke the MDI command: G10 L20 P0 [AXIS] [VALUE],
. wait for it to complete,
. switch back to the original mode,
. emit a 'reload-display' signal.

*do_jog* :: '(axis_number,direction, distance)' -
This will jog an axis continuously or at a set distance. +
You must be in the proper mode to jog.

*check_for_modes* :: '(mode)' -
This function checks for required LinuxCNC mode. +
It returns a Python tuple (state, mode) +
mode will be set the mode the system is in +
state will set to:
- false if mode is 0
- false if machine is busy
- true if LinuxCNC is in the requested mode
- None if possible to change, but not in requested mode

*get_current_mode* :: '(nothing)' -
returns integer: the current LinuxCNC mode. +

*set_selected_joint* :: '(integer)' -
records the selected joint number internally. +
requests the joint to be selected by emitting the +
'joint-selection-changed' message.

*get_selected_joint* :: '(None)' -
returns integer representing the internal selected joint number.

*set_selected_axis* :: '(string)' -
records the selected axis letter internally. +
Requests the axis to be selected by emitting the `axis-selection-changed` message.

*get_selected_axis* :: '(None)' -
returns string representing the internal selected axis letter.

*is_man_mode* :: '(None)' -

*is_mdi_mode* :: '(None)' -

*is_auto_mode* :: '(None)' -

*is_on_and_idle* :: '(None)' -

*is_auto_running* :: '(None)' -

*is_auto_paused* :: '(None)' -

*is_file_loaded* :: '(None)' -

*is_metric_mode* :: '(None)' -

*is_spindle_on* :: '(None)' -

*shutdown* :: '(None)' -

== Known Issues

Some status points are reported wrongly during a running program because the interpreter runs ahead of the current position of a running program.
This will hopefully be resolved with the merge of state-tags branch.

// vim: set syntax=asciidoc: