cFS(Core Flight System)を動かす(Tutorial)

# 1. CoreFlightExecutive (cFE)とは
cFEはNASAのGODDARDが開発をしているアプリケーション開発及びランタイム(実行)環境を提供するソフトウェアである。このcFEは、Softaware Bus(Messaging)、Time、Event(Alerts)、Executive(Startup and runtime)、テーブルサービスなどのコアサービスが含まれており、これらの機能を用いてユーザはプログラミングを行うことができる。このcFEを用いればスケーラブルかつ高信頼なソフトウェアを容易に開発できるということが利点である。設計思想はコンポーネント指向型のソフトウェア構造に似ている。

特徴的な点を挙げるとすれば、cFEにはOSAL(OS Abstraction Layer)というレイヤーが存在しており、このレイヤーがOSと上位レイヤーの緩衝材(抽象化)となるため、様々なOSを差し替えても動作するという点だろう。

https://opensource.gsfc.nasa.gov/projects/cfe/index.php

詳しい仕組みについては、以下を参照
https://qiita.com/harmegiddo/items/e13b5f1312b792d97efa


#2. 動作環境
OS: CentOS-6.8-i386/Ubuntu 16.04.3 LTS x86_64/ Ubuntu 17.10 on xorg
(Req: 32 bit or 64 bit executable on a 64 bit system, x86 Linux)
cFE: cFE-6.5.0a
OSAL: 4.2.0 or higher

https://sourceforge.net/projects/coreflightexec/
https://github.com/nasa/osal


#3. 動かす

###3.1. 必要なソフトウェアをぶちこんでいく
````
$ cd YOUR_PROJECT
$ wget https://downloads.sourceforge.net/project/coreflightexec/cFE-6.5.0a-OSS-release.tar.gz
$ tar -zxf cFE-6.5.0a-OSS-release.tar.gz
$ wget https://downloads.sourceforge.net/project/osal/osal-4.2.0-OSS-release.tar.gz
$ tar -zxf osal-4.2.0-OSS-release.tar.gz
$ mv osal-4.2.0-release/ osal
$ rm -rf cFE-6.5.0-OSS-release/osal/
$ mv osal/ cFE-6.5.0-OSS-release/
$ sudo apt-get install build-essential
````

###3.2. ビルドしていく

````
$ cd cFE-6.5.0-OSS-release/
$ . ./setvars.sh
$ cd build/cpu1/
$ make config
````

なお、ここでsetvarsを動作させる際には、「.」を忘れないこと。
また、64bit系だと``make config``の際に以下のようなエラーがでる。

````
/usr/include/features.h:367:25: fatal error: sys/cdefs.h: No such file or directory
compilation terminated.
Makefile:49: recipe for target 'elf2cfetbl' failed
make[1]: *** [elf2cfetbl] Error 1
make[1]: Leaving directory '/workspace/cfe/cFE-6.5.0-OSS-release/build/cpu1/elf2cfetbl'
../cfs.mak:383: recipe for target 'cfs_tool' failed
make: *** [cfs_tool] Error 2
````

なので、32bit系のライブラリをぶちこんであげること。

````
# 64bit only
$ sudo apt-get install libc6-dev-i386
````

そして、最後にビルド

````
$ make
````

###3.3. 起動

下記で実行させる。なおパスは``YOUR_PROJECT/cFE-6.5.0-OSS-release/build/cpu1/``配下とする。

````
$ cd exe
$ sudo ./core-linux.bin 
````

OSALはPOSIXメッセージキューを使用してタスク間通信を実装しており、デフォルト設定よりも大きなlinuxのmsg_maxパラメータを必要としている。このため、管理者実行をする、もしくは``/proc/sys/fs/mqueue/msg_max``のパラメータを増やすこと。


そんなこんなで、こんな感じで起動すれば成功。

````
CFE_PSP: Default Reset Type = PO
CFE_PSP: Default Reset SubType = 1
CFE_PSP: Default CPU ID = 1
CFE_PSP: Default Spacecraft ID = 66
CFE_PSP: Default CPU Name: CPU1
CFE_PSP: Starting the cFE with a POWER ON reset.
CFE_PSP: Clearing out CFE CDS Shared memory segment.
CFE_PSP: Clearing out CFE Reset Shared memory segment.
CFE_PSP: Clearing out CFE User Reserved Shared memory segment.
2027-305-06:16:08.41752 POWER ON RESET due to Power Cycle (Power Cycle).
2027-305-06:16:08.41759 ES Startup: CFE_ES_Main in EARLY_INIT state
CFE_PSP: CFE_PSP_AttachExceptions Called
2027-305-06:16:08.41762 ES Startup: CFE_ES_Main entering CORE_STARTUP state
2027-305-06:16:08.41763 ES Startup: Starting Object Creation calls.
2027-305-06:16:08.41764 ES Startup: Calling CFE_ES_CDSEarlyInit
2027-305-06:16:08.41773 ES Startup: Calling CFE_EVS_EarlyInit
2027-305-06:16:08.41777 Event Log cleared following power-on reset
2027-305-06:16:08.41779 ES Startup: Calling CFE_SB_EarlyInit
2027-305-06:16:08.41809 ES Startup: Calling CFE_TIME_EarlyInit
````

##4. つつく
cFEを起動させた状態で``CommandSystem.py``を動作させてみる。これはどこにあるかというと、``/YOUR_PROJECT/cFE-6.5.0-OSS-release/tools/cFS-GroundSystem/Subsystems/cmdGui/``の中にある。

困ったことにPython2.7に対応しているので、Python3.0以上の人はコードを書き直す必要がある。以下が書きなおしたコード。なお、書き直してまで動かしたくない人は、ここを飛ばしてください。

CommandSystem.py

````
#!/usr/bin/env python
#
import sys
import csv
import subprocess
import shlex
import pickle

from PyQt4 import QtGui
from CommandSystemDialog import Ui_CommandSystemDialog
from struct import *


class CommandSystem(QtGui.QDialog):

    #
    # Init the class
    #
    def __init__(self):
        QtGui.QDialog.__init__(self)
        self.ui = Ui_CommandSystemDialog()
        self.ui.setupUi(self)
        self.setWindowTitle('Command System Main Page')
        self.move(800,100)

        self.ui.pushButton_0.clicked.connect(self.ProcessButton_0)
        self.ui.pushButton_1.clicked.connect(self.ProcessButton_1)
        self.ui.pushButton_2.clicked.connect(self.ProcessButton_2)
        self.ui.pushButton_3.clicked.connect(self.ProcessButton_3)
        self.ui.pushButton_4.clicked.connect(self.ProcessButton_4)
        self.ui.pushButton_5.clicked.connect(self.ProcessButton_5)
        self.ui.pushButton_6.clicked.connect(self.ProcessButton_6)
        self.ui.pushButton_7.clicked.connect(self.ProcessButton_7)
        self.ui.pushButton_8.clicked.connect(self.ProcessButton_8)
        self.ui.pushButton_9.clicked.connect(self.ProcessButton_9)
        self.ui.pushButton_10.clicked.connect(self.ProcessButton_10)
        self.ui.pushButton_11.clicked.connect(self.ProcessButton_11)
        self.ui.pushButton_12.clicked.connect(self.ProcessButton_12)
        self.ui.pushButton_13.clicked.connect(self.ProcessButton_13)
        self.ui.pushButton_14.clicked.connect(self.ProcessButton_14)
        self.ui.pushButton_15.clicked.connect(self.ProcessButton_15)
        self.ui.pushButton_16.clicked.connect(self.ProcessButton_16)
        self.ui.pushButton_17.clicked.connect(self.ProcessButton_17)
        self.ui.pushButton_18.clicked.connect(self.ProcessButton_18)
        self.ui.pushButton_19.clicked.connect(self.ProcessButton_19)
        self.ui.pushButton_20.clicked.connect(self.ProcessButton_20)

        self.ui.quickButton_1.clicked.connect(self.QuickButton_1)
        self.ui.quickButton_2.clicked.connect(self.QuickButton_2)
        self.ui.quickButton_3.clicked.connect(self.QuickButton_3)
        self.ui.quickButton_4.clicked.connect(self.QuickButton_4)
        self.ui.quickButton_5.clicked.connect(self.QuickButton_5)
        self.ui.quickButton_6.clicked.connect(self.QuickButton_6)
        self.ui.quickButton_7.clicked.connect(self.QuickButton_7)
        self.ui.quickButton_8.clicked.connect(self.QuickButton_8)
        self.ui.quickButton_9.clicked.connect(self.QuickButton_9)
        self.ui.quickButton_10.clicked.connect(self.QuickButton_10)
        self.ui.quickButton_11.clicked.connect(self.QuickButton_11)
        self.ui.quickButton_12.clicked.connect(self.QuickButton_12)
        self.ui.quickButton_13.clicked.connect(self.QuickButton_13)
        self.ui.quickButton_14.clicked.connect(self.QuickButton_14)
        self.ui.quickButton_15.clicked.connect(self.QuickButton_15)
        self.ui.quickButton_16.clicked.connect(self.QuickButton_16)
        self.ui.quickButton_17.clicked.connect(self.QuickButton_17)
        self.ui.quickButton_18.clicked.connect(self.QuickButton_18)
        self.ui.quickButton_19.clicked.connect(self.QuickButton_19)
        self.ui.quickButton_20.clicked.connect(self.QuickButton_20)
        self.ui.quickButton_21.clicked.connect(self.QuickButton_21)
        self.ui.quickButton_22.clicked.connect(self.QuickButton_22)



    #
    # Display button wrappers
    #
    def ProcessButton_0(self):
        self.ProcessButtonGeneric(0)
    def ProcessButton_1(self):
        self.ProcessButtonGeneric(1)
    def ProcessButton_2(self):
        self.ProcessButtonGeneric(2)
    def ProcessButton_3(self):
        self.ProcessButtonGeneric(3)
    def ProcessButton_4(self):
        self.ProcessButtonGeneric(4)
        self.ProcessButtonGeneric(6)
    def ProcessButton_7(self):
        self.ProcessButtonGeneric(7)
    def ProcessButton_8(self):
        self.ProcessButtonGeneric(8)
    def ProcessButton_9(self):
        self.ProcessButtonGeneric(9)
    def ProcessButton_10(self):
        self.ProcessButtonGeneric(10)
    def ProcessButton_11(self):
        self.ProcessButtonGeneric(11)
    def ProcessButton_12(self):
        self.ProcessButtonGeneric(12)
    def ProcessButton_13(self):
        self.ProcessButtonGeneric(13)
    def ProcessButton_14(self):
        self.ProcessButtonGeneric(14)
    def ProcessButton_15(self):
        self.ProcessButtonGeneric(15)
    def ProcessButton_16(self):
        self.ProcessButtonGeneric(16)
    def ProcessButton_17(self):
        self.ProcessButtonGeneric(17)
    def ProcessButton_18(self):
        self.ProcessButtonGeneric(18)
    def ProcessButton_19(self):
        self.ProcessButtonGeneric(19)
    def ProcessButton_20(self):
        self.ProcessButtonGeneric(20)

    #
    # Processes 'Display Page' button
    #
    def ProcessButtonGeneric(self, idx):
        if cmdPageIsValid[idx] == True:
           lineEditPktId = getattr(Command.ui, 'lineEditPktId_'+str(idx))
           lineEditAddress = getattr(Command.ui, 'lineEdit_'+str(idx))
           pktId = str(lineEditPktId.text())
           address = str(lineEditAddress.text())
           cmd_args = shlex.split(launch_string)
           print (launch_string)
           subprocess.Popen(cmd_args)

    #
    # Quick button wrappers
    #
    def QuickButton_1(self):
        self.ProcessQuickButton(0)
    def QuickButton_2(self):
        self.ProcessQuickButton(1)
    def QuickButton_3(self):
        self.ProcessQuickButton(2)
    def QuickButton_4(self):
        self.ProcessQuickButton(3)
    def QuickButton_5(self):
        self.ProcessQuickButton(4)
    def QuickButton_6(self):
        self.ProcessQuickButton(6)
    def QuickButton_8(self):
        self.ProcessQuickButton(7)
    def QuickButton_9(self):
        self.ProcessQuickButton(8)
    def QuickButton_10(self):
        self.ProcessQuickButton(9)
    def QuickButton_11(self):
        self.ProcessQuickButton(10)
    def QuickButton_12(self):
        self.ProcessQuickButton(11)
    def QuickButton_13(self):
        self.ProcessQuickButton(12)
    def QuickButton_14(self):
        self.ProcessQuickButton(13)
    def QuickButton_15(self):
        self.ProcessQuickButton(14)
    def QuickButton_16(self):
        self.ProcessQuickButton(15)
    def QuickButton_17(self):
        self.ProcessQuickButton(16)
    def QuickButton_18(self):
    def QuickButton_20(self):
        self.ProcessQuickButton(19)
    def QuickButton_21(self):


    #
    # Determines if command requires parameters
    #
    def checkParams(self, idx):
        pickle_file = 'ParameterFiles/' + quickParam[idx]
        try:
            with open(pickle_file,'rb') as pickle_obj:
                if len(paramNames) > 0: # if has parameters
                   return True
                else:
                   return False
        except IOError:
            return False


    #
    # Processes quick button
    #
    def ProcessQuickButton(self, idx):
        if cmdPageIsValid[idx] == True and quickIndices[idx] >= 0:
           quickIdx = quickIndices[idx]
           lineEditPktId = getattr(Command.ui, 'lineEditPktId_'+str(idx))
           lineEditAddress = getattr(Command.ui, 'lineEdit_'+str(idx))
           pktId = str(lineEditPktId.text())
           address = str(lineEditAddress.text())

           # if requires parameters
           if self.checkParams(quickIdx) == True:
              prog = 'python Parameter.py'

           # if doesn't require parameters
           else:

           # print launch_string
           cmd_args = shlex.split(launch_string)
           subprocess.Popen(cmd_args)


#
# Main
#
if __name__ == '__main__':

    #
    # Set defaults for the arguments
    #
    cmdDefFile = "command-pages.txt"

    #
    # Init the QT application and the telemetry dialog class
    #
    app = QtGui.QApplication(sys.argv)
    Command = CommandSystem()

    #
    # Read in the contents of the telemetry packet defintion
    #
    cmdPageIsValid = []
    cmdPageDesc = []
    cmdPageDefFile = []
    cmdPageAppid = []
    cmdPageEndian = []
    cmdClass = []
    cmdPageAddress = []
    cmdPagePort = []

    i = 0

    with open(cmdDefFile, 'rb') as cmdfile:
        reader = csv.reader(cmdfile, skipinitialspace = True)
        for cmdRow in reader:
            try:
                if cmdRow[0][0] != '#':
                    cmdPageIsValid.append(True)
                    cmdPageDesc.append(cmdRow[0])
                    cmdPageDefFile.append(cmdRow[1])
                    cmdPageAppid.append(int(cmdRow[2],16))
                    cmdPageEndian.append(cmdRow[3])
                    cmdClass.append(cmdRow[4])
                    cmdPageAddress.append(cmdRow[5])
                    cmdPagePort.append(int(cmdRow[6]))
                    i += 1
            except IndexError:
                print ("IndexError: list index out of range")
                print ("This could be due to improper formatting in command-pages.txt.")
                print ("This is a common error caused by blank lines in command-pages.txt")

    #
    # Mark the remaining values as invalid
    #
    for j in range (i, 22):
       cmdPageAppid.append(0)
       cmdPageIsValid.append(False)


    #
    # Read in contents of quick button definition file
    #
    quickDefFile = 'quick-buttons.txt'
    subsys = []
    subsysFile = []
    quickCmd = []
    quickCode = []
    quickPktId = []
    quickEndian = []
    quickAddress = []
    quickPort = []
    quickParam = []
    quickIndices = []

    with open(quickDefFile,'rb') as subFile:
       reader = csv.reader(subFile)
       i = 0
       for fileRow in reader:
          if fileRow[0][0] != '#':
             subsys.append(fileRow[0])
             subsysFile.append(fileRow[1])
             quickCmd.append(fileRow[2].strip())
             quickCode.append(fileRow[3].strip())
             quickPktId.append(fileRow[4].strip())
             quickEndian.append(fileRow[5].strip())
             quickAddress.append(fileRow[6].strip())
             quickPort.append(fileRow[7].strip())
             quickParam.append(fileRow[8].strip())
             i+=1

    #
    # fill the data fields on the page
    #
    if cmdPageIsValid[0] == True:
       Command.ui.SubsysBrowser_0.setText(cmdPageDesc[0])
       Command.ui.lineEditPktId_0.setText(hex(cmdPageAppid[0]))
       Command.ui.lineEdit_0.setText(cmdPageAddress[0])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[0])
          Command.ui.quickButton_1.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_0.setText("(unused)")

    if cmdPageIsValid[1] == True:
       Command.ui.SubsysBrowser_1.setText(cmdPageDesc[1])
       Command.ui.lineEditPktId_1.setText(hex(cmdPageAppid[1]))
       Command.ui.lineEdit_1.setText(cmdPageAddress[1])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[1])
          Command.ui.quickButton_2.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_1.setText("(unused)")

    if cmdPageIsValid[2] == True:
       Command.ui.SubsysBrowser_2.setText(cmdPageDesc[2])
       Command.ui.lineEditPktId_2.setText(hex(cmdPageAppid[2]))
       Command.ui.lineEdit_2.setText(cmdPageAddress[2])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[2])
          Command.ui.quickButton_3.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_2.setText("(unused)")

    if cmdPageIsValid[3] == True:
       Command.ui.SubsysBrowser_3.setText(cmdPageDesc[3])
       Command.ui.lineEditPktId_3.setText(hex(cmdPageAppid[3]))
       Command.ui.lineEdit_3.setText(cmdPageAddress[3])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[3])
          Command.ui.quickButton_4.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_3.setText("(unused)")

    if cmdPageIsValid[4] == True:
       Command.ui.SubsysBrowser_4.setText(cmdPageDesc[4])
       Command.ui.lineEditPktId_4.setText(hex(cmdPageAppid[4]))
       Command.ui.lineEdit_4.setText(cmdPageAddress[4])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[4])
          Command.ui.quickButton_5.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_4.setText("(unused)")

    if cmdPageIsValid[5] == True:
       Command.ui.SubsysBrowser_5.setText(cmdPageDesc[5])
       Command.ui.lineEditPktId_5.setText(hex(cmdPageAppid[5]))
       Command.ui.lineEdit_5.setText(cmdPageAddress[5])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[5])
          Command.ui.quickButton_6.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_5.setText("(unused)")

    if cmdPageIsValid[6] == True:
       Command.ui.SubsysBrowser_6.setText(cmdPageDesc[6])
       Command.ui.lineEditPktId_6.setText(hex(cmdPageAppid[6]))
       Command.ui.lineEdit_6.setText(cmdPageAddress[6])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[6])
          Command.ui.quickButton_7.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_6.setText("(unused)")

    if cmdPageIsValid[7] == True:
       Command.ui.SubsysBrowser_7.setText(cmdPageDesc[7])
       Command.ui.lineEditPktId_7.setText(hex(cmdPageAppid[7]))
       Command.ui.lineEdit_7.setText(cmdPageAddress[7])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[7])
          Command.ui.quickButton_8.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_7.setText("(unused)")

    if cmdPageIsValid[8] == True:
       Command.ui.SubsysBrowser_8.setText(cmdPageDesc[8])
       Command.ui.lineEditPktId_8.setText(hex(cmdPageAppid[8]))
       Command.ui.lineEdit_8.setText(cmdPageAddress[8])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[8])
          Command.ui.quickButton_9.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_8.setText("(unused)")

    if cmdPageIsValid[9] == True:
       Command.ui.SubsysBrowser_9.setText(cmdPageDesc[9])
       Command.ui.lineEditPktId_9.setText(hex(cmdPageAppid[9]))
       Command.ui.lineEdit_9.setText(cmdPageAddress[9])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[9])
          Command.ui.quickButton_10.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_9.setText("(unused)")

    if cmdPageIsValid[10] == True:
       Command.ui.SubsysBrowser_10.setText(cmdPageDesc[10])
       Command.ui.lineEditPktId_10.setText(hex(cmdPageAppid[10]))
       Command.ui.lineEdit_10.setText(cmdPageAddress[10])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[10])
          Command.ui.quickButton_11.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_10.setText("(unused)")

    if cmdPageIsValid[11] == True:
       Command.ui.SubsysBrowser_11.setText(cmdPageDesc[11])
       Command.ui.lineEditPktId_11.setText(hex(cmdPageAppid[11]))
       Command.ui.lineEdit_11.setText(cmdPageAddress[11])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[11])
          Command.ui.quickButton_12.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_11.setText("(unused)")

    if cmdPageIsValid[12] == True:
       Command.ui.SubsysBrowser_12.setText(cmdPageDesc[12])
       Command.ui.lineEditPktId_12.setText(hex(cmdPageAppid[12]))
       Command.ui.lineEdit_12.setText(cmdPageAddress[12])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[12])
          Command.ui.quickButton_13.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_12.setText("(unused)")

    if cmdPageIsValid[13] == True:
       Command.ui.SubsysBrowser_13.setText(cmdPageDesc[13])
       Command.ui.lineEditPktId_13.setText(hex(cmdPageAppid[13]))
       Command.ui.lineEdit_13.setText(cmdPageAddress[13])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[13])
          Command.ui.quickButton_14.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_13.setText("(unused)")

    if cmdPageIsValid[14] == True:
       Command.ui.SubsysBrowser_14.setText(cmdPageDesc[14])
       Command.ui.lineEditPktId_14.setText(hex(cmdPageAppid[14]))
       Command.ui.lineEdit_14.setText(cmdPageAddress[14])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[14])
          Command.ui.quickButton_15.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_14.setText("(unused)")

    if cmdPageIsValid[15] == True:
       Command.ui.SubsysBrowser_15.setText(cmdPageDesc[15])
       Command.ui.lineEditPktId_15.setText(hex(cmdPageAppid[15]))
       Command.ui.lineEdit_15.setText(cmdPageAddress[15])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[15])
          Command.ui.quickButton_16.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_15.setText("(unused)")

    if cmdPageIsValid[16] == True:
       Command.ui.SubsysBrowser_16.setText(cmdPageDesc[16])
       Command.ui.lineEditPktId_16.setText(hex(cmdPageAppid[16]))
       Command.ui.lineEdit_16.setText(cmdPageAddress[16])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[16])
          Command.ui.quickButton_17.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_16.setText("(unused)")

    if cmdPageIsValid[17] == True:
       Command.ui.SubsysBrowser_17.setText(cmdPageDesc[17])
       Command.ui.lineEditPktId_17.setText(hex(cmdPageAppid[17]))
       Command.ui.lineEdit_17.setText(cmdPageAddress[17])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[17])
          Command.ui.quickButton_18.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_17.setText("(unused)")

    if cmdPageIsValid[18] == True:
       Command.ui.SubsysBrowser_18.setText(cmdPageDesc[18])
       Command.ui.lineEditPktId_18.setText(hex(cmdPageAppid[18]))
       Command.ui.lineEdit_18.setText(cmdPageAddress[18])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[18])
          Command.ui.quickButton_19.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_18.setText("(unused)")

    if cmdPageIsValid[19] == True:
       Command.ui.SubsysBrowser_19.setText(cmdPageDesc[19])
       Command.ui.lineEditPktId_19.setText(hex(cmdPageAppid[19]))
       Command.ui.lineEdit_19.setText(cmdPageAddress[19])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[19])
          Command.ui.quickButton_20.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_19.setText("(unused)")

    if cmdPageIsValid[20] == True:
       Command.ui.SubsysBrowser_20.setText(cmdPageDesc[20])
       Command.ui.lineEditPktId_20.setText(hex(cmdPageAppid[20]))
       Command.ui.lineEdit_20.setText(cmdPageAddress[20])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[20])
          Command.ui.quickButton_21.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_20.setText("(unused)")

    if cmdPageIsValid[21] == True:
       Command.ui.SubsysBrowser_21.setText(cmdPageDesc[21])
       Command.ui.lineEditPktId_21.setText(hex(cmdPageAppid[21]))
       Command.ui.lineEdit_21.setText(cmdPageAddress[21])
       quickIdx = -1
       try:
          quickIdx = subsys.index(cmdPageDesc[21])
          Command.ui.quickButton_22.setText(quickCmd[quickIdx])
       except ValueError:
          pass
       quickIndices.append(quickIdx)
    else:
       Command.ui.SubsysBrowser_21.setText("(unused)")


    #
    # Display the page
    #
    Command.show()
    Command.raise_()
    print ('Command System started.')
    sys.exit(app.exec_())
````

動かしてみる。

````
$ python CommandSystem.py
````
##4.1. 動かないなら環境を整える
Python 2.7 and QT4の環境が必要になる。このため、Anacondaで諸々ぶちこむ。
以下のURLからインストールシェルをダウンロード

https://www.continuum.io/downloads

次に、シェルの起動

````
$ bash Anaconda3-4.3.0-Linux-x86_64.sh
````

ライセンス関係などについて聞かれるが、エンターを押しまくればインストールできる。
パスが通ってないのでbashrcの最後あたりに追記。
何らかの既存環境と競合する場合は削除。

````
$ export PATH=/YOUR_HOME_PATH/anaconda3/bin:$PATH
````

環境を作って、切換え

````
$ conda create -n py27a python=2.7 pyqt=4 anaconda
$ source activate py27a
````

もしpyqt5が入っていたらダウングレードする

````
$ conda list | grep pyqt
 -> pyqt 5.6.0
$ conda uninstall pyqt
$ conda install pyqt=4
$ conda list | grep pyqt
 -> pyqt 4.1.1
$ python ./CommandSystem.py
````

zmqが入っていない場合は入れてあげる

````
$ pip install pyzmq
````

上手く動けばこんな感じ

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/9119ba3a-6c38-c608-cc88-d611072ff263.png)


##4.2. Errorを殺していく

テキトーに、コマンドを送ろうとポチポチ選択していくとエラーが発生する。

子プロセスの生成のところ。

````
Traceback (most recent call last):
  File "UdpCommands.py", line 72, in ProcessSendButton_1
    self.ProcessSendButtonGeneric(0)
  File "UdpCommands.py", line 159, in ProcessSendButtonGeneric
    subprocess.Popen(cmd_args)
  File "/home/aphrodite/anaconda3/envs/py27a/lib/python2.7/subprocess.py", line 390, in __init__
    errread, errwrite)
  File "/home/aphrodite/anaconda3/envs/py27a/lib/python2.7/subprocess.py", line 1025, in _execute_child
    raise child_exception
````

では修正していく。``$ vim UdpCommands.py``


UdpCommands.py - 159

````
 subprocess.Popen(cmd_args)
````

↓

````
 subprocess.Popen(cmd_args, shell=False)
````

実行したいコマンドが文字列、配列の場合はフラグを入れてあげる。
直接起動させたいんだろうと意図を汲む。

そして、またエラーがでる。

````
python UdpCommands.py --title="Executive Services (CPU1)" --pktid=0x1806 --file=cfe__es__msg_8h --address="127.0.0.1" --port=1234 --endian=LE
/usr/share/themes/Ambiance/gtk-2.0/apps/mate-panel.rc:30: error: invalid string constant "murrine-scrollbar", expected valid string constant
--host=127.0.0.1: 1: --host=127.0.0.1: ../cmdUtil/cmdUtil: not found
````

どうやらcmdUtilというバイナリがあるらしい。``/cFE-6.5.0-OSS-release/tools/cFS-GroundSystem/Subsystems/cmdUtil``へ移動し、``make``をぶちかます。

無事、cmdUtilができたことを確認する。

ここでもう一度起動する。
すると上手くいく。

なお、``subprocess``をTrueにしている場合は以下のようなエラーがでる。

```
Error: Need to specify a Packet ID using the --pktid or -I option.
```

デバッグついでに、``UdpCommands.py`` の中から``subprocess``コマンドを発行しているところを探して、printする。

以下が出力された。

````
['../cmdUtil/cmdUtil', '--host=127.0.0.1', '--port=1234', '--pktid=0x1806', '--endian=LE', '--cmdcode=12']
````

なるほど引き数の構造が分かった。じゃあ、Pythonから発行されているであろうShellを手動で動かしてみる。

コマンド:

```
$ ../cmdUtil/cmdUtil --host=127.0.0.1 --port=1234 --pktid=0x1806 --endian=LE --cmdcode=12
```

なおパスは``cFE-6.5.0-OSS-release/tools/cFS-GroundSystem/Subsystems/cmdGui``

標準出力:

```
st=127.0.0.1 --port=1234 --pktid=0x1806 --endian=LE --cmdcode=12
Host: 127.0.0.1
Port: 1234
Pkt ID: 0x1806
sending data to '127.0.0.1' (IP : 127.0.0.1); port 1234
Data to send:
0x18 0x06 0xC0 0x00 0x00 0x01 0x00 0x0C 
```

動いた。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/a8fd4b74-283b-92f0-88cc-b72eb18730fb.png)

cFEの方も反応しているので、これは成功。

とにかくエラーが出て困る場合は、Teble ServiceのParameter Dialogも以下のように開くとよい。

```
python Parameter.py --title="Table Services (CPU1)" --descrip=Dump Table --idx=3 --host=127.0.0.1 --port=1234 --pktid=0x1804 --endian=LE --cmdcode=3 --file=struct_c_f_e___t_b_l___dump_cmd__t
```

取りあえず、``shell=False``を入れておけばこんなことにはならないはず。

# 5. 相互通信をしてみる
Errorを殺し続けた人なら次のアプリケーションが動くだろう。
それはテレコマのアプリケーションである。


``/YOUR_PROJECT/cFE-6.5.0-OSS-release/tools/cFS-GroundSystem/GroundSystem.py``を起動する。

次にStart Command System -> Enable Tlm -> Inputに127.0.0.1と入力
その後、Start Telemetry Systemを起動すると、テレメトリの受信を確認できるはずである。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/aefe9a89-ca73-d840-dee3-d4d45a6e4545.png)


後は、このPythonコードを基に色々とソースコードをいじっていけば
ソフトウェアバスを利用したプログラムを容易に開発することができる。


# 6. 簡単なFDIRの実装
今回はストーリーとして、以下を想定する。
1. 地上側で衛星のとあるアプリケーションのエラーを発見する
2. アンテナ局から衛星に向けて、とあるアプリケーションへIsolationコマンドを送信する
3. コマンドを受信したアプリケーションは即座に自身を終了させる
これを実装する。

衛星側のプログラム (application層): 
既存のSample_Appをこんな感じに書き換える。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/c5786ae2-26e1-0e61-cf8d-f7c820771074.png)

割り当てられているMsgId (すなわちpacket id)は0x1882がコマンド実行で、0x1883がテレメの送信である。
このプログラムはMsgIdを0x1882(コマンド実行モード)を受信して、コマンドコードが100の場合に異常を検知したとして、自身のApplicationを終了するというプログラムである。

地上局側のプログラム: 
こんな感じでアンテナ局を模倣し、コマンドを送る。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/83d8c36e-72a7-56dc-ebb7-c9e317503bd1.png)

衛星側のcFS: 
Isolationが実行された時のcFSの実行ログである。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/d52b5c5c-9bc4-78a7-11aa-7836f82ded57.png)
cFSが地上局からコマンドを受信し、定められたApplicationへ配信、cFSからパケットを受信したApplicationが自身を終了(CFE_ES_ExitApp)する。
これによってcFS側がSAMPLE_APPを実行できなくなり、cFS側はUnkown Stateを出力している。


FDIRには様々なバリエーションがある。
前提として異常はアプリケーションで起こるとした際に、その検知、分離、復旧はアプリケーションでやるのか、そもそもそれより下層で行うかである。また異常についてもアプリケーション自体がFreeze, Destroyするような異常なのか、アプリケーションの固有機能に何らかの異常がおきているのか、これによっても処置が変わってくる。このバリエーションを実装してみて思ったことは、ハイブリッドによるFDIRが良いのではないかと考える。

例えば、以下のような形。
まず、ある機能Aを遂行するアプリケーションAを作成する。次に、そのアプリケーションAが正常に動作しているかをチェックするアプリケーションA_Testを作成する。アプリケーションが持つソフトウェア構造の状態が正常に動作しているときは、アプリケーションA_TestがアプリケーションAの異常を検知、分離、復旧をする。次に、アプリケーションが持つソフトウェア構造の状態が正常に動作していないときは、アプリケーション層より下の層(OSとか)で、それを検知、分離、復旧する。これには、アプリケーションA_Testも含まれるものとする。

このような形式が、ソースコードの観点からも分離できて保守性が高くなるかなと考えた。

さて、皆さんはどのようなFDIRを実装する？
cFSを使って色々な実装をしてみましょう。