AETHER-2846 Support all parameters which lists in Aether Docs

AETHER-2847 Integrating with Prometheus and record eNB information
AETHER-2848 Move SAS configuration as enodeb base, not plugin in driver code
AETHER-2879 add gps information in prometheus
AETHER-2880 add ip and port as configurable parameter in enodebd
AETHER-2897 Firmware update feature over CWMP
AETHER-3022 Integrate firmware upgrade state into configuration workflow
AETHER-3120 Develop ACS state machine with firmware upgrade feature

Change-Id: I0bcbf2229ba3c1638f2a997f3c651f8d6240145d
diff --git a/.gitignore b/.gitignore
index 200ed83..bbd439d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,12 @@
 lte/
 orc8r/
+prometheus.yml
 metrics_pb2.py
 metrics_pb2_grpc.py
 
+### Log ###
+*.log
+
 ### Python ###
 # Byte-compiled / optimized / DLL files
 __pycache__/
diff --git a/README.md b/README.md
index 60b0c16..a176859 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,13 @@
 
 eNodeB daemon is an Automatic Configuration Server (ACS) which forks from [Facebook Magma project](https://github.com/magma/magma). It currently tested with Sercomm eNodeB P27-SCE4255W small cell.
 
+# Run
+
+```
+make venv
+python main.py
+```
+
 # Configuration
 
 We have these configuration files for configuring eNodeBD service.
@@ -17,4 +24,29 @@
 
 3. magma_configs/serial_numbers/_2009CW5000019_.yml
 
-The `serial_number.yml` will have the customized value for each configurable parameters. The value in `serial_number.yml` will override the value defines in `gateway.mconfig` and `acs_common.yml` when corresponding eNodeB connects (base on the serial number provided by eNodeB).
\ No newline at end of file
+The `serial_number.yml` will have the customized value for each configurable parameters. The value in `serial_number.yml` will override the value defines in `gateway.mconfig` and `acs_common.yml` when corresponding eNodeB connects (base on the serial number provided by eNodeB).
+
+# Prometheus Support
+
+The following example configuration can start a Prometheus container for monitoring data sent by enodebd. The enodebd will start prometheus server on port 8000 by default.
+
+```bash
+# prometheus configuration - prometheus.yml
+global:
+  scrape_interval: 15s
+  external_labels:
+    monitor: 'codelab-monitor'
+
+scrape_configs:
+  - job_name: 'prometheus'
+    scrape_interval: 5s
+    static_configs:
+      - targets: ['172.17.0.1:8000']
+```
+
+... and start prometheus container with docker command.
+
+```
+$ docker run -p 9090:9090 -v /home/ubuntu/magma-enodebd-new/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus
+```
+
diff --git a/common/cert_utils.py b/common/cert_utils.py
index 3be5284..e19063f 100644
--- a/common/cert_utils.py
+++ b/common/cert_utils.py
@@ -20,6 +20,25 @@
 from common.serialization_utils import write_to_file_atomically
 
 
+def load_key_bytes(key_file):
+    """Load a private key encoded in PEM format
+
+    Args:
+        key_file: path to the key file
+
+    Returns:
+        Bytes
+
+    Raises:
+        IOError: If file cannot be opened
+        ValueError: If the file content cannot be decoded successfully
+        TypeError: If the key_file is encrypted
+    """
+    with open(key_file, 'rb') as f:
+        key_bytes = f.read()
+
+    return key_bytes
+
 def load_key(key_file):
     """Load a private key encoded in PEM format
 
@@ -128,6 +147,23 @@
 
     return csr
 
+def load_cert_bytes(cert_file):
+    """Load certificate from a file
+
+    Args:
+        cert_file: path to file storing the cert in PEM format
+
+    Returns:
+        cert: an instance of x509.Certificate
+
+    Raises:
+        IOError: If file cannot be opened
+        ValueError: If the file content cannot be decoded successfully
+    """
+    with open(cert_file, 'rb') as f:
+        cert_pem = f.read()
+
+    return cert_pem
 
 def load_cert(cert_file):
     """Load certificate from a file
diff --git a/common/service.py b/common/service.py
index 5a3ca98..59f06ca 100644
--- a/common/service.py
+++ b/common/service.py
@@ -230,6 +230,7 @@
         is received or a StopService rpc call is made on the Service303
         interface.
         """
+
         logging.info("Starting %s...", self._name)
         (host, port) = ServiceRegistry.get_service_address(self._name)
         self._port = self._server.add_insecure_port('{}:{}'.format(host, port))
diff --git a/data_models/data_model_parameters.py b/data_models/data_model_parameters.py
index 27df4d9..751b351 100644
--- a/data_models/data_model_parameters.py
+++ b/data_models/data_model_parameters.py
@@ -31,20 +31,37 @@
 
     SERIAL_NUMBER = 'Serial number'
     CELL_ID = 'Cell ID'
+    IP_ADDRESS = "ip_address"
 
     # Capabilities
     DUPLEX_MODE_CAPABILITY = 'Duplex mode capability'
     BAND_CAPABILITY = 'Band capability'
 
     # RF-related parameters
-    EARFCNDL = 'EARFCNDL'
-    EARFCNUL = 'EARFCNUL'
+    EARFCNDL = 'earfcndl1'
+    EARFCNUL = 'earfcnul1'
+    EARFCNDL2 = 'earfcndl2'
+    EARFCNUL2 = 'earfcnul2'
+    EARFCNDL_LIST = 'earfcndl_list'
+    EARFCNUL_LIST = 'earfcnul_list'
+
+    FREQ_BAND_1 = "freq_band_1"
+    FREQ_BAND_2 = "freq_band_2"
+    FREQ_BAND_LIST = "freq_band_list"
+
     BAND = 'Band'
     PCI = 'PCI'
     DL_BANDWIDTH = 'DL bandwidth'
     UL_BANDWIDTH = 'UL bandwidth'
     SUBFRAME_ASSIGNMENT = 'Subframe assignment'
     SPECIAL_SUBFRAME_PATTERN = 'Special subframe pattern'
+    TX_POWER = "tx_power"
+    TUNNEL_TYPE = "tunnel_type"
+
+    # Radio Resource Management (RRM) parameters
+    CARRIER_AGG_ENABLE = "carrier_agg_enable"
+    CARRIER_NUMBER = "carrier_number"
+    CONTIGUOUS_CC = "contiguous_cc"
 
     # Other LTE parameters
     ADMIN_STATE = 'Admin state'
@@ -54,6 +71,7 @@
     # RAN parameters
     CELL_RESERVED = 'Cell reserved'
     CELL_BARRED = 'Cell barred'
+    PRIM_SOURCE = "prim_source"
 
     # Core network parameters
     MME_IP = 'MME IP'
@@ -70,13 +88,15 @@
     PLMN_N_PLMNID = 'PLMN %d PLMNID'
 
     # PLMN arrays are added below
-    TAC = 'TAC'
+    TAC = 'tac'
+    TAC2 = 'tac2'
     IP_SEC_ENABLE = 'IPSec enable'
     MME_POOL_ENABLE = 'MME pool enable'
 
     # Management server parameters
     PERIODIC_INFORM_ENABLE = 'Periodic inform enable'
     PERIODIC_INFORM_INTERVAL = 'Periodic inform interval'
+    ENABLE_CWMP = "enable_cwmp"
 
     # Performance management parameters
     PERF_MGMT_ENABLE = 'Perf mgmt enable'
@@ -85,6 +105,33 @@
     PERF_MGMT_USER = 'Perf mgmt username'
     PERF_MGMT_PASSWORD = 'Perf mgmt password'
 
+    SAS_ENABLE = "sas_enabled"
+    SAS_SERVER_URL = "sas_server_url"
+    SAS_UID = "sas_uid"
+    SAS_CATEGORY = "sas_category"
+    SAS_CHANNEL_TYPE = "sas_channel_type"
+    SAS_CERT_SUBJECT = "sas_cert_subject"
+    SAS_IC_GROUP_ID = "sas_icg_group_id"
+    SAS_LOCATION = "sas_location"
+    SAS_HEIGHT_TYPE = "sas_height_type"
+    SAS_FCCID = "sas_fccid"
+    SAS_MEAS_CAPS = "sas_measure_capability"
+    SAS_MANU_ENABLE = "sas_manufacturer_prefix_enable"
+
+    SAS_CPI_ENABLE = "sas_cpi_enable"
+    SAS_CPI_IPE = "sas_cpi_ipe"
+    SAS_CPI_NAME = "sas_cpi_name"
+    SAS_CPI_ID = "sas_cpi_id"
+    SAS_CPI_DATA = "sas_cpi_signature_data"
+    SAS_ANTA_AZIMUTH = "sas_antenna_azimuth"
+    SAS_ANTA_DOWNTILT = "sas_antenna_downtilt"
+    SAS_ANTA_GAIN = "sas_antenna_gain"
+    SAS_ANTA_BEAMWIDTH = "sas_antenna_beamwidth"
+
+    FIRMWARE_VERSION = "firmware_version"
+    FIRMWARE_URL = "firmware_url"
+    FIRMWARE_SIZE = "firmware_size"
+
 
 class TrParameterType():
     BOOLEAN = 'boolean'
diff --git a/device_config/configuration_init.py b/device_config/configuration_init.py
index 4b156df..2c4e342 100644
--- a/device_config/configuration_init.py
+++ b/device_config/configuration_init.py
@@ -91,6 +91,8 @@
     enb_config = _get_enb_yang_config(device_config) or \
                  _get_enb_config(mconfig, device_config)
 
+    print(enb_config)
+
     _set_earfcn_freq_band_mode(
         device_config, cfg_desired, data_model,
         enb_config.earfcndl,
@@ -190,7 +192,6 @@
         mconfig: mconfigs_pb2.EnodebD,
         device_config: EnodebConfiguration,
 ) -> SingleEnodebConfig:
-
     # The eNodeB parameters to be generated with default value,
     # It will load from eNB configs based on serial number or default value
     # The params is a nested list which contains 2 format of parameter names.
@@ -237,8 +238,13 @@
     Set the following parameters:
      - PCI
     """
-    if pci not in range(0, 504 + 1):
+
+    if pci is int and pci not in range(0, 504 + 1):
         raise ConfigurationError('Invalid PCI (%d)' % pci)
+
+    if pci is str and any(map(lambda x: int(x) not in range(0, 504 + 1), pci.split(","))):
+        raise ConfigurationError('Invalid PCI (%s)' % pci)
+
     cfg.set_parameter(ParameterName.PCI, pci)
 
 
diff --git a/devices/freedomfi_one.py b/devices/freedomfi_one.py
index bfa66eb..4dfee90 100644
--- a/devices/freedomfi_one.py
+++ b/devices/freedomfi_one.py
@@ -48,6 +48,9 @@
     EndSessionState,
     EnodebAcsState,
     ErrorState,
+    DownloadState,
+    WaitDownloadResponseState,
+    WaitInformTransferCompleteState,
     GetParametersState,
     SetParameterValuesState,
     WaitGetParametersState,
@@ -59,113 +62,239 @@
 from tr069 import models
 
 
+FAPSERVICE_PATH = "Device.Services.FAPService.1."
+FAP_CONTROL = FAPSERVICE_PATH + "FAPControl."
+
+
+class FreedomFiOneHandler(BasicEnodebAcsStateMachine):
+    def __init__(self, service: MagmaService,) -> None:
+        self._state_map = {}
+        super().__init__(service=service, use_param_key=True)
+
+    def reboot_asap(self) -> None:
+        self.transition("reboot")
+
+    def is_enodeb_connected(self) -> bool:
+        return not isinstance(self.state, WaitInformState)
+
+    def _init_state_map(self) -> None:
+        self._state_map = {
+            # Inform comes in -> Respond with InformResponse
+            "wait_inform": WaitInformState(self, when_done="get_rpc_methods"),
+            # If first inform after boot -> GetRpc request comes in, if not
+            # empty request comes in => Transition to get_transient_params
+            "get_rpc_methods": FreedomFiOneGetInitState(
+                self, when_done="get_transient_params",
+            ),
+            # Read transient readonly params.
+            "get_transient_params": FreedomFiOneSendGetTransientParametersState(
+                self, when_upgrade="firmware_upgrade", when_done="get_params",
+            ),
+            "firmware_upgrade": DownloadState(self, when_done="wait_download_response"),
+            "wait_download_response": WaitDownloadResponseState(
+                self, when_done="wait_transfer_complete"
+            ),
+            "wait_transfer_complete": WaitInformTransferCompleteState(
+                self,
+                when_done="get_params",
+                when_periodic="wait_transfer_complete",
+                when_timeout="end_session",
+            ),
+            "get_params": FreedomFiOneGetObjectParametersState(
+                self,
+                when_delete="delete_objs",
+                when_add="add_objs",
+                when_set="set_params",
+                when_skip="end_session",
+            ),
+            "delete_objs": DeleteObjectsState(
+                self, when_add="add_objs", when_skip="set_params",
+            ),
+            "add_objs": AddObjectsState(self, when_done="set_params"),
+            "set_params": SetParameterValuesState(self, when_done="wait_set_params",),
+            "wait_set_params": WaitSetParameterValuesState(
+                self,
+                when_done="check_get_params",
+                when_apply_invasive="check_get_params",
+                status_non_zero_allowed=True,
+            ),
+            "check_get_params": GetParametersState(
+                self, when_done="check_wait_get_params", request_all_params=True,
+            ),
+            "check_wait_get_params": WaitGetParametersState(
+                self, when_done="end_session",
+            ),
+            "end_session": EndSessionState(self),
+            # These states are only entered through manual user intervention
+            "reboot": EnbSendRebootState(self, when_done="wait_reboot"),
+            "wait_reboot": WaitRebootResponseState(
+                self, when_done="wait_post_reboot_inform",
+            ),
+            "wait_post_reboot_inform": WaitInformMRebootState(
+                self, when_done="wait_empty", when_timeout="wait_inform",
+            ),
+            # The states below are entered when an unexpected message type is
+            # received
+            "unexpected_fault": ErrorState(
+                self, inform_transition_target="wait_inform",
+            ),
+        }
+
+    @property
+    def device_name(self) -> str:
+        return EnodebDeviceName.FREEDOMFI_ONE
+
+    @property
+    def data_model_class(self) -> Type[DataModel]:
+        return FreedomFiOneTrDataModel
+
+    @property
+    def config_postprocessor(self) -> EnodebConfigurationPostProcessor:
+        return FreedomFiOneConfigurationInitializer(self)
+
+    @property
+    def state_map(self) -> Dict[str, EnodebAcsState]:
+        return self._state_map
+
+    @property
+    def disconnected_state_name(self) -> str:
+        return "wait_inform"
+
+    @property
+    def unexpected_fault_state_name(self) -> str:
+        return "unexpected_fault"
+
+
 class SASParameters:
     """ Class modeling the SAS parameters and their TR path"""
 
-    # SAS parameters for FreedomFiOne
-    FAP_CONTROL = "Device.Services.FAPService.1.FAPControl."
-    FAPSERVICE_PATH = "Device.Services.FAPService.1."
-
-    # Sas management parameters
-    SAS_ENABLE = "sas_enabled"
-    SAS_SERVER_URL = "sas_server_url"
-    SAS_UID = "sas_uid"
-    SAS_CATEGORY = "sas_category"
-    SAS_CHANNEL_TYPE = "sas_channel_type"
-    SAS_CERT_SUBJECT = "sas_cert_subject"
-    SAS_IC_GROUP_ID = "sas_icg_group_id"
-    SAS_LOCATION = "sas_location"
-    SAS_HEIGHT_TYPE = "sas_height_type"
-    SAS_CPI_ENABLE = "sas_cpi_enable"
-    SAS_CPI_IPE = "sas_cpi_ipe"  # Install param supplied enable
-    FREQ_BAND_1 = "freq_band_1"
-    FREQ_BAND_2 = "freq_band_2"
     # For CBRS radios we set this to the limit and the SAS can reduce the
     # power if needed.
-    TX_POWER_CONFIG = "tx_power_config"
 
     SAS_PARAMETERS = {
-        SAS_ENABLE: TrParam(
-            FAP_CONTROL + "LTE.X_000E8F_SAS.Enable",
+        ParameterName.SAS_ENABLE: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.Enable",
             is_invasive=False,
             type=TrParameterType.BOOLEAN,
             is_optional=False,
         ),
-        SAS_SERVER_URL: TrParam(
-            FAP_CONTROL + "LTE.X_000E8F_SAS.Server",
+        ParameterName.SAS_SERVER_URL: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.Server",
             is_invasive=False,
             type=TrParameterType.STRING,
             is_optional=False,
         ),
-        SAS_UID: TrParam(
-            FAP_CONTROL + "LTE.X_000E8F_SAS.UserContactInformation",
+        ParameterName.SAS_UID: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.UserContactInformation",
             is_invasive=False,
             type=TrParameterType.STRING,
             is_optional=False,
         ),
-        SAS_CATEGORY: TrParam(
-            FAP_CONTROL + "LTE.X_000E8F_SAS.Category",
+        ParameterName.SAS_CATEGORY: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.Category",
             is_invasive=False,
             type=TrParameterType.STRING,
             is_optional=False,
         ),
-        SAS_CHANNEL_TYPE: TrParam(
-            FAP_CONTROL + "LTE.X_000E8F_SAS.ProtectionLevel",
+        ParameterName.SAS_CHANNEL_TYPE: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.ProtectionLevel",
             is_invasive=False,
             type=TrParameterType.STRING,
             is_optional=False,
         ),
-        SAS_CERT_SUBJECT: TrParam(
-            FAP_CONTROL + "LTE.X_000E8F_SAS.CertSubject",
+        ParameterName.SAS_CERT_SUBJECT: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.CertSubject",
             is_invasive=False,
             type=TrParameterType.STRING,
             is_optional=False,
         ),
         # SAS_IC_GROUP_ID: TrParam(
-        #     FAP_CONTROL + 'LTE.X_000E8F_SAS.ICGGroupId', is_invasive=False,
+        #     FAP_CONTROL + 'LTE.X_SCM_SAS.ICGGroupId', is_invasive=False,
         #     type=TrParameterType.STRING, False),
-        SAS_LOCATION: TrParam(
-            FAP_CONTROL + "LTE.X_000E8F_SAS.Location",
+        ParameterName.SAS_LOCATION: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.Location",
             is_invasive=False,
             type=TrParameterType.STRING,
             is_optional=False,
         ),
-        SAS_HEIGHT_TYPE: TrParam(
-            FAP_CONTROL + "LTE.X_000E8F_SAS.HeightType",
+        ParameterName.SAS_HEIGHT_TYPE: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.HeightType",
             is_invasive=False,
             type=TrParameterType.STRING,
             is_optional=False,
         ),
-        SAS_CPI_ENABLE: TrParam(
-            FAP_CONTROL + "LTE.X_000E8F_SAS.CPIEnable",
+        ParameterName.SAS_CPI_ENABLE: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.CPIEnable",
             is_invasive=False,
             type=TrParameterType.BOOLEAN,
             is_optional=False,
         ),
-        SAS_CPI_IPE: TrParam(
-            FAP_CONTROL + "LTE.X_000E8F_SAS.CPIInstallParamSuppliedEnable",
+        ParameterName.SAS_CPI_IPE: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.CPIInstallParamSuppliedEnable",
             is_invasive=False,
             type=TrParameterType.BOOLEAN,
             is_optional=False,
         ),
-        FREQ_BAND_1: TrParam(
-            FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.FreqBandIndicator",
+        ParameterName.SAS_FCCID: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.FCCIdentificationNumber",
             is_invasive=False,
-            type=TrParameterType.UNSIGNED_INT,
+            type=TrParameterType.STRING,
             is_optional=False,
         ),
-        FREQ_BAND_2: TrParam(
-            FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.X_000E8F_FreqBandIndicator2",
+        ParameterName.SAS_MEAS_CAPS: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.MeasCapability",
             is_invasive=False,
-            type=TrParameterType.UNSIGNED_INT,
+            type=TrParameterType.STRING,
             is_optional=False,
         ),
-        TX_POWER_CONFIG: TrParam(
-            FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.X_000E8F_TxPowerConfig",
-            is_invasive=True,
+        ParameterName.SAS_MANU_ENABLE: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.ManufacturerPrefixEnable",
+            is_invasive=False,
+            type=TrParameterType.BOOLEAN,
+            is_optional=False,
+        ),
+        ParameterName.SAS_CPI_NAME: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.CPIName",
+            is_invasive=False,
+            type=TrParameterType.STRING,
+            is_optional=False,
+        ),
+        ParameterName.SAS_CPI_ID: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.CPIId",
+            is_invasive=False,
+            type=TrParameterType.STRING,
+            is_optional=False,
+        ),
+        ParameterName.SAS_ANTA_AZIMUTH: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.AntennaAzimuth",
+            is_invasive=False,
             type=TrParameterType.INT,
             is_optional=False,
         ),
+        ParameterName.SAS_ANTA_DOWNTILT: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.AntennaDowntilt",
+            is_invasive=False,
+            type=TrParameterType.INT,
+            is_optional=False,
+        ),
+        ParameterName.SAS_ANTA_GAIN: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.AntennaGain",
+            is_invasive=False,
+            type=TrParameterType.INT,
+            is_optional=False,
+        ),
+        ParameterName.SAS_ANTA_BEAMWIDTH: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.AntennaBeamwidth",
+            is_invasive=False,
+            type=TrParameterType.INT,
+            is_optional=False,
+        ),
+        ParameterName.SAS_CPI_DATA: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_SAS.CPISignatureData",
+            is_invasive=False,
+            type=TrParameterType.STRING,
+            is_optional=False,
+        ),
     }
 
 
@@ -177,10 +306,6 @@
     and converting it to Magma understood fields.
     """
 
-    DEFGW_STATUS_PATH = "Device.X_SCM_DeviceFeature.X_SCM_NEStatus.X_SCM_DEFGW_Status"
-    SAS_STATUS_PATH = "Device.Services.FAPService.1.FAPControl.LTE.X_SCM_SAS.State"
-    ENB_STATUS_PATH = "Device.X_SCM_DeviceFeature.X_SCM_NEStatus.X_SCM_eNB_Status"
-
     # Status parameters
     DEFAULT_GW = "defaultGW"
     SYNC_STATUS = "syncStatus"
@@ -190,30 +315,18 @@
 
     STATUS_PARAMETERS = {
         # Status nodes
-        # This works
         DEFAULT_GW: TrParam(
-            DEFGW_STATUS_PATH,
+            "Device.X_SCM_DeviceFeature.X_SCM_NEStatus.X_SCM_DEFGW_Status",
             is_invasive=False,
             type=TrParameterType.STRING,
             is_optional=False,
         ),
-        # SYNC_STATUS: TrParam(
-        #     STATUS_PATH + 'X_000E8F_Sync_Status', is_invasive=False,
-        #     type=TrParameterType.STRING, is_optional=False,
-        # ),
-        # This works
         SAS_STATUS: TrParam(
-            SAS_STATUS_PATH,
+            "Device.Services.FAPService.1.FAPControl.LTE.X_SCM_SAS.State",
             is_invasive=False,
             type=TrParameterType.STRING,
             is_optional=False,
         ),
-        # This doesn't work
-        # ENB_STATUS: TrParam(
-        #     ENB_STATUS_PATH, is_invasive=False,
-        #     type=TrParameterType.STRING, is_optional=False,
-        # ),
-        # GPS status, lat, long
         GPS_SCAN_STATUS: TrParam(
             "Device.FAP.GPS.ScanStatus",
             is_invasive=False,
@@ -232,6 +345,18 @@
             type=TrParameterType.STRING,
             is_optional=False,
         ),
+        ParameterName.SW_VERSION: TrParam(
+            "Device.DeviceInfo.SoftwareVersion",
+            is_invasive=False,
+            type=TrParameterType.STRING,
+            is_optional=False,
+        ),
+        ParameterName.SERIAL_NUMBER: TrParam(
+            "Device.DeviceInfo.SerialNumber",
+            is_invasive=False,
+            type=TrParameterType.STRING,
+            is_optional=False,
+        ),
     }
 
     # Derived status params that don't have tr69 representation.
@@ -374,6 +499,8 @@
             )
 
         pass_through_params = [ParameterName.GPS_LAT, ParameterName.GPS_LONG]
+
+        print(name_to_val)
         for name in pass_through_params:
             device_cfg.set_parameter(name, name_to_val[name])
 
@@ -384,159 +511,39 @@
     miscellaneous properties
     """
 
-    FAP_CONTROL = "Device.Services.FAPService.1.FAPControl."
-    FAPSERVICE_PATH = "Device.Services.FAPService.1."
-
-    # Tunnel ref format clobber it to non IPSEC as we don't support
-    # IPSEC
-    TUNNEL_REF = "tunnel_ref"
-    PRIM_SOURCE = "prim_src"
-
-    # Carrier aggregation
-    CARRIER_AGG_ENABLE = "carrier_agg_enable"
-    CARRIER_NUMBER = "carrier_number"  # Carrier aggregation params
-    CONTIGUOUS_CC = "contiguous_cc"
-    WEB_UI_ENABLE = "web_ui_enable"  # Enable or disable local enb UI
-
     MISC_PARAMETERS = {
-        # WEB_UI_ENABLE: TrParam(
-        #     'Device.X_000E8F_DeviceFeature.X_000E8F_WebServerEnable',
-        #     is_invasive=False,
-        #     type=TrParameterType.BOOLEAN, is_optional=False,
-        # ),
-        CARRIER_AGG_ENABLE: TrParam(
-            FAP_CONTROL + "LTE.X_000E8F_RRMConfig.X_000E8F_CA_Enable",
+        ParameterName.CARRIER_AGG_ENABLE: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_RRMConfig.X_SCM_CA_Enable",
             is_invasive=False,
             type=TrParameterType.BOOLEAN,
             is_optional=False,
         ),
-        CARRIER_NUMBER: TrParam(
-            FAP_CONTROL + "LTE.X_000E8F_RRMConfig.X_000E8F_Cell_Number",
+        ParameterName.CARRIER_NUMBER: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_RRMConfig.X_SCM_Cell_Number",
             is_invasive=False,
             type=TrParameterType.INT,
             is_optional=False,
         ),
-        CONTIGUOUS_CC: TrParam(
-            FAP_CONTROL + "LTE.X_000E8F_RRMConfig.X_000E8F_CELL_Freq_Contiguous",
+        ParameterName.CONTIGUOUS_CC: TrParam(
+            FAP_CONTROL + "LTE.X_SCM_RRMConfig.X_SCM_CELL_Freq_Contiguous",
             is_invasive=False,
             type=TrParameterType.INT,
             is_optional=False,
         ),
-        TUNNEL_REF: TrParam(
-            FAPSERVICE_PATH + "CellConfig.LTE.Tunnel.1.TunnelRef",
+        ParameterName.PRIM_SOURCE: TrParam(
+            FAPSERVICE_PATH + "REM.X_SCM_tfcsManagerConfig.primSrc",
             is_invasive=False,
             type=TrParameterType.STRING,
             is_optional=False,
         ),
-        PRIM_SOURCE: TrParam(
-            FAPSERVICE_PATH + "REM.X_000E8F_tfcsManagerConfig.primSrc",
+        ParameterName.ENABLE_CWMP: TrParam(
+            "Device.ManagementServer.EnableCWMP",
             is_invasive=False,
-            type=TrParameterType.STRING,
+            type=TrParameterType.BOOLEAN,
             is_optional=False,
         ),
     }
 
-    # Hardcoded defaults
-    defaults = {
-        # Use IPV4 only
-        TUNNEL_REF: "Device.IP.Interface.1.IPv4Address.1.",
-        # Only synchronize with GPS
-        PRIM_SOURCE: "FREE_RUNNING",
-        # Always enable carrier aggregation for the CBRS bands
-        CARRIER_AGG_ENABLE: False,
-        CARRIER_NUMBER: 1,  # CBRS has two carriers
-        CONTIGUOUS_CC: 0,  # Its not contiguous carrier
-    }
-
-
-class FreedomFiOneHandler(BasicEnodebAcsStateMachine):
-    def __init__(self, service: MagmaService,) -> None:
-        self._state_map = {}
-        super().__init__(service=service, use_param_key=True)
-
-    def reboot_asap(self) -> None:
-        self.transition("reboot")
-
-    def is_enodeb_connected(self) -> bool:
-        return not isinstance(self.state, WaitInformState)
-
-    def _init_state_map(self) -> None:
-        self._state_map = {
-            # Inform comes in -> Respond with InformResponse
-            "wait_inform": WaitInformState(self, when_done="get_rpc_methods"),
-            # If first inform after boot -> GetRpc request comes in, if not
-            # empty request comes in => Transition to get_transient_params
-            "get_rpc_methods": FreedomFiOneGetInitState(
-                self, when_done="get_transient_params",
-            ),
-            # Read transient readonly params.
-            "get_transient_params": FreedomFiOneSendGetTransientParametersState(
-                self, when_done="get_params",
-            ),
-            "get_params": FreedomFiOneGetObjectParametersState(
-                self,
-                when_delete="delete_objs",
-                when_add="add_objs",
-                when_set="set_params",
-                when_skip="end_session",
-            ),
-            "delete_objs": DeleteObjectsState(
-                self, when_add="add_objs", when_skip="set_params",
-            ),
-            "add_objs": AddObjectsState(self, when_done="set_params"),
-            "set_params": SetParameterValuesState(self, when_done="wait_set_params",),
-            "wait_set_params": WaitSetParameterValuesState(
-                self,
-                when_done="check_get_params",
-                when_apply_invasive="check_get_params",
-                status_non_zero_allowed=True,
-            ),
-            "check_get_params": GetParametersState(
-                self, when_done="check_wait_get_params", request_all_params=True,
-            ),
-            "check_wait_get_params": WaitGetParametersState(
-                self, when_done="end_session",
-            ),
-            "end_session": EndSessionState(self),
-            # These states are only entered through manual user intervention
-            "reboot": EnbSendRebootState(self, when_done="wait_reboot"),
-            "wait_reboot": WaitRebootResponseState(
-                self, when_done="wait_post_reboot_inform",
-            ),
-            "wait_post_reboot_inform": WaitInformMRebootState(
-                self, when_done="wait_empty", when_timeout="wait_inform",
-            ),
-            # The states below are entered when an unexpected message type is
-            # received
-            "unexpected_fault": ErrorState(
-                self, inform_transition_target="wait_inform",
-            ),
-        }
-
-    @property
-    def device_name(self) -> str:
-        return EnodebDeviceName.FREEDOMFI_ONE
-
-    @property
-    def data_model_class(self) -> Type[DataModel]:
-        return FreedomFiOneTrDataModel
-
-    @property
-    def config_postprocessor(self) -> EnodebConfigurationPostProcessor:
-        return FreedomFiOneConfigurationInitializer(self)
-
-    @property
-    def state_map(self) -> Dict[str, EnodebAcsState]:
-        return self._state_map
-
-    @property
-    def disconnected_state_name(self) -> str:
-        return "wait_inform"
-
-    @property
-    def unexpected_fault_state_name(self) -> str:
-        return "unexpected_fault"
-
 
 class FreedomFiOneTrDataModel(DataModel):
     """
@@ -555,16 +562,10 @@
     - Num PLMNs is not reported by these units
     """
 
-    # Mapping of TR parameter paths to aliases
-    DEVICE_PATH = "Device."
-    FAPSERVICE_PATH = DEVICE_PATH + "Services.FAPService.1."
-    FAP_CONTROL = FAPSERVICE_PATH + "FAPControl."
-    BCCH = FAPSERVICE_PATH + "REM.LTE.Cell.1.BCCH."
-
     PARAMETERS = {
         # Top-level objects
         ParameterName.DEVICE: TrParam(
-            DEVICE_PATH,
+            "Device.",
             is_invasive=False,
             type=TrParameterType.OBJECT,
             is_optional=False,
@@ -576,25 +577,67 @@
             is_optional=False,
         ),
         # Device info
-        ParameterName.SW_VERSION: TrParam(
-            DEVICE_PATH + "DeviceInfo.SoftwareVersion",
-            is_invasive=False,
-            type=TrParameterType.STRING,
-            is_optional=False,
-        ),
-        ParameterName.SERIAL_NUMBER: TrParam(
-            DEVICE_PATH + "DeviceInfo.SerialNumber",
+        ParameterName.IP_ADDRESS: TrParam(
+            "Device.IP.Interface.1.IPv4Address.1.IPAddress",
             is_invasive=False,
             type=TrParameterType.STRING,
             is_optional=False,
         ),
         # RF-related parameters
+        ParameterName.FREQ_BAND_1: TrParam(
+            FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.FreqBandIndicator",
+            is_invasive=False,
+            type=TrParameterType.UNSIGNED_INT,
+            is_optional=False,
+        ),
+        ParameterName.FREQ_BAND_2: TrParam(
+            FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.X_SCM_FreqBandIndicator2",
+            is_invasive=False,
+            type=TrParameterType.UNSIGNED_INT,
+            is_optional=False,
+        ),
+        ParameterName.FREQ_BAND_LIST: TrParam(
+            FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.X_SCM_FreqBandIndicatorConfigList",
+            is_invasive=False,
+            type=TrParameterType.STRING,
+            is_optional=False,
+        ),
         ParameterName.EARFCNDL: TrParam(
             FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.EARFCNDL",
             is_invasive=False,
             type=TrParameterType.INT,
             is_optional=False,
         ),
+        ParameterName.EARFCNUL: TrParam(
+            FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.EARFCNUL",
+            is_invasive=False,
+            type=TrParameterType.INT,
+            is_optional=False,
+        ),
+        ParameterName.EARFCNDL2: TrParam(
+            FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.X_SCM_EARFCNDL2",
+            is_invasive=False,
+            type=TrParameterType.INT,
+            is_optional=False,
+        ),
+        ParameterName.EARFCNUL2: TrParam(
+            FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.X_SCM_EARFCNUL2",
+            is_invasive=False,
+            type=TrParameterType.INT,
+            is_optional=False,
+        ),
+        ParameterName.EARFCNDL_LIST: TrParam(
+            FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.X_SCM_EARFCNDLConfigList",
+            is_invasive=False,
+            type=TrParameterType.STRING,
+            is_optional=False,
+        ),
+        ParameterName.EARFCNUL_LIST: TrParam(
+            FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.X_SCM_EARFCNULConfigList",
+            is_invasive=False,
+            type=TrParameterType.STRING,
+            is_optional=False,
+        ),
         ParameterName.DL_BANDWIDTH: TrParam(
             FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.DLBandwidth",
             is_invasive=False,
@@ -639,7 +682,7 @@
             is_optional=False,
         ),
         ParameterName.GPS_ENABLE: TrParam(
-            DEVICE_PATH + "FAP.GPS.ScanOnBoot",
+            "Device.FAP.GPS.ScanOnBoot",
             is_invasive=False,
             type=TrParameterType.BOOLEAN,
             is_optional=False,
@@ -657,46 +700,58 @@
             type=TrParameterType.INT,
             is_optional=False,
         ),
-        # It may not work, comment out first
-        # ParameterName.NUM_PLMNS: TrParam(
-        #     FAPSERVICE_PATH + 'CellConfig.LTE.EPC.PLMNListNumberOfEntries',
-        #     is_invasive=False,
-        #     type=TrParameterType.INT, is_optional=False,
-        # ),
         ParameterName.TAC: TrParam(
             FAPSERVICE_PATH + "CellConfig.LTE.EPC.TAC",
             is_invasive=False,
             type=TrParameterType.INT,
             is_optional=False,
         ),
+        ParameterName.TAC2: TrParam(
+            FAPSERVICE_PATH + "CellConfig.LTE.EPC.X_SCM_TAC2",
+            is_invasive=False,
+            type=TrParameterType.INT,
+            is_optional=False,
+        ),
         # Management server parameters
         ParameterName.PERIODIC_INFORM_ENABLE: TrParam(
-            DEVICE_PATH + "ManagementServer.PeriodicInformEnable",
+            "Device.ManagementServer.PeriodicInformEnable",
             is_invasive=False,
             type=TrParameterType.BOOLEAN,
             is_optional=False,
         ),
         ParameterName.PERIODIC_INFORM_INTERVAL: TrParam(
-            DEVICE_PATH + "ManagementServer.PeriodicInformInterval",
+            "Device.ManagementServer.PeriodicInformInterval",
             is_invasive=False,
             type=TrParameterType.INT,
             is_optional=False,
         ),
         # Performance management parameters
         ParameterName.PERF_MGMT_ENABLE: TrParam(
-            DEVICE_PATH + "FAP.PerfMgmt.Config.1.Enable",
+            "Device.FAP.PerfMgmt.Config.1.Enable",
             is_invasive=False,
             type=TrParameterType.BOOLEAN,
             is_optional=False,
         ),
         ParameterName.PERF_MGMT_UPLOAD_INTERVAL: TrParam(
-            DEVICE_PATH + "FAP.PerfMgmt.Config.1.PeriodicUploadInterval",
+            "Device.FAP.PerfMgmt.Config.1.PeriodicUploadInterval",
             is_invasive=False,
             type=TrParameterType.INT,
             is_optional=False,
         ),
         ParameterName.PERF_MGMT_UPLOAD_URL: TrParam(
-            DEVICE_PATH + "FAP.PerfMgmt.Config.1.URL",
+            "Device.FAP.PerfMgmt.Config.1.URL",
+            is_invasive=False,
+            type=TrParameterType.STRING,
+            is_optional=False,
+        ),
+        ParameterName.TX_POWER: TrParam(
+            FAPSERVICE_PATH + "CellConfig.LTE.RAN.RF.X_SCM_TxPowerConfig",
+            is_invasive=True,
+            type=TrParameterType.INT,
+            is_optional=False,
+        ),
+        ParameterName.TUNNEL_TYPE: TrParam(
+            FAPSERVICE_PATH + "CellConfig.LTE.Tunnel.1.TunnelRef",
             is_invasive=False,
             type=TrParameterType.STRING,
             is_optional=False,
@@ -740,7 +795,6 @@
     PARAMETERS.update(SASParameters.SAS_PARAMETERS)
     PARAMETERS.update(FreedomFiOneMiscParameters.MISC_PARAMETERS)
     PARAMETERS.update(StatusParameters.STATUS_PARAMETERS)
-    # These are stateful parameters that have no tr-69 representation
     PARAMETERS.update(StatusParameters.DERIVED_STATUS_PARAMETERS)
 
     TRANSFORMS_FOR_MAGMA = {
@@ -812,7 +866,6 @@
     """
 
     SAS_KEY = "sas"
-    WEB_UI_ENABLE_LIST_KEY = "web_ui_enable_list"
 
     def __init__(self, acs: EnodebAcsStateMachine):
         super().__init__()
@@ -821,29 +874,9 @@
     def postprocess(
         self, mconfig: Any, service_cfg: Any, desired_cfg: EnodebConfiguration,
     ) -> None:
-
-        desired_cfg.delete_parameter(ParameterName.EARFCNDL)
-        desired_cfg.delete_parameter(ParameterName.DL_BANDWIDTH)
-        desired_cfg.delete_parameter(ParameterName.UL_BANDWIDTH)
-
-        # go through misc parameters and set them to default.
-        for name, val in FreedomFiOneMiscParameters.defaults.items():
-            desired_cfg.set_parameter(name, val)
-
         # Bump up the parameter key version
         self.acs.parameter_version_inc()
 
-        if self.WEB_UI_ENABLE_LIST_KEY in service_cfg:
-            serial_nos = service_cfg.get(self.WEB_UI_ENABLE_LIST_KEY)
-            if self.acs.device_cfg.has_parameter(ParameterName.SERIAL_NUMBER,):
-                if self.acs.get_parameter(ParameterName.SERIAL_NUMBER) in serial_nos:
-                    desired_cfg.set_parameter(
-                        FreedomFiOneMiscParameters.WEB_UI_ENABLE, True,
-                    )
-            else:
-                # This should not happen
-                EnodebdLogger.error("Serial number unknown for device")
-
         # Load eNB customized configuration from "./magma_config/serial_number/"
         # and configure each connected eNB based on serial number
         enbcfg = load_enb_config()
@@ -866,9 +899,10 @@
     Some eNB parameters are read only and updated by the eNB itself.
     """
 
-    def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
+    def __init__(self, acs: EnodebAcsStateMachine, when_upgrade: str, when_done: str):
         super().__init__()
         self.acs = acs
+        self.upgrade_transition = when_upgrade
         self.done_transition = when_done
 
     def get_msg(self, message: Any) -> AcsMsgAndTransition:
@@ -877,11 +911,6 @@
         request.ParameterNames = models.ParameterNames()
         request.ParameterNames.string = []
 
-        # request = models.GetParameterNames()
-        # request.ParameterPath = "Device."
-        # request.NextLevel = False
-
-        # Get the status parameters which was defined in Line 171
         for _, tr_param in StatusParameters.STATUS_PARAMETERS.items():
             path = tr_param.path
             request.ParameterNames.string.append(path)
@@ -905,7 +934,15 @@
             name_to_val, self.acs.device_cfg,
         )
 
-        return AcsReadMsgResult(msg_handled=True, next_state=self.done_transition,)
+        print("In get transient state", name_to_val, type(name_to_val))
+        if name_to_val["SW version"] != "TEST3918@210224":
+            print("Get into Firmware Upgrade state")
+            return AcsReadMsgResult(
+                msg_handled=True, next_state=self.upgrade_transition
+            )
+
+        print("Skip firmware upgrade, configure the enb")
+        return AcsReadMsgResult(msg_handled=True, next_state=self.done_transition)
 
     def state_description(self) -> str:
         return "Getting transient read-only parameters"
@@ -1005,6 +1042,8 @@
             obj_name = ParameterName.PLMN_N % i
             desired = obj_to_params[obj_name]
             names += desired
+
+        print(obj_to_params)
         return names
 
     def get_msg(self, message: Any) -> AcsMsgAndTransition:
diff --git a/magma_configs/enodebd.yml b/magma_configs/enodebd.yml
index ff5fe6e..346a6ed 100644
--- a/magma_configs/enodebd.yml
+++ b/magma_configs/enodebd.yml
@@ -13,6 +13,10 @@
 #
 # log_level is set in mconfig. It can be overridden here
 
+prometheus:
+  ip: 0.0.0.0
+  port: 8000
+
 tr069:
   interface: eth0 # NOTE: this value must be consistent with dnsmasq.conf
   port: 48080
@@ -21,6 +25,10 @@
   #       if this is ever changed in dnsd.yml, this needs to be updated too
   public_ip: 18.116.99.179
 
+ssl:
+  key: "./enodebd.key"
+  cert: "./enodebd.cert"
+
 # TODO: @amar: This is a temp workaround to allow for testing until we
 # connect enodebd with the domain proxy which is responsible for talking to
 # SAS.
@@ -46,4 +54,3 @@
 
 # Network interface to terminate S1
 s1_interface: eth1
-
diff --git a/magma_configs/magmad.yml b/magma_configs/magmad.yml
index ccc7ff9..ecbafc4 100644
--- a/magma_configs/magmad.yml
+++ b/magma_configs/magmad.yml
@@ -18,47 +18,7 @@
 
 # List of services for magmad to control
 magma_services:
-  # - control_proxy
-  # - subscriberdb
-  # - mobilityd
-  # - directoryd
   - enodebd
-  # - sessiond
-  # - mme
-  # - pipelined
-  # - envoy_controller
-  # - redis
-  # - dnsd
-  # - policydb
-  # - state
-  # - eventd
-  # - smsd
-  # - ctraced
-  # - health
-  # - kernsnoopd
-  # - liagentd
-
-# List of services that don't provide service303 interface
-# non_service303_services:
-  # - control_proxy
-  # - dnsd
-  # - redis
-  # - td-agent-bit
-
-# List of all possible dynamic services (enabled from gateway.mconfig)
-# registered_dynamic_services:
-  # - redirectd
-  # - td-agent-bit
-  # - monitord
-  # - dpid
-
-# A list of group of services which are linked together in systemd
-# linked_services:
-  # -
-  #  - mme
-  #  - pipelined
-  #  - mobilityd
-  #  - sessiond
 
 # list of services that are required to have meta before checking in
 # (meta = data gathered via MagmaService.register_get_status_callback())
@@ -126,7 +86,6 @@
     - pipelined
     - state
     - sessiond
-#    - kernsnoopd
 
 generic_command_config:
   module: magma.magmad.generic_command.shell_command_executor
diff --git a/magma_configs/serial_number/2009CW5000019.yml b/magma_configs/serial_number/2009CW5000019.yml
index 0b0e52f..140781f 100644
--- a/magma_configs/serial_number/2009CW5000019.yml
+++ b/magma_configs/serial_number/2009CW5000019.yml
@@ -2,10 +2,9 @@
 specialSubframePattern: 7
 earfcndl: 44490
 plmnidList: "305010"
-pci: 501
+pci: "100,101"
 allowEnodebTransmit: False
 subframeAssignment: 2
-tac: 2
 cell_id: 1
 mme_address: 172.21.143.206
 mme_port: 36412
@@ -17,13 +16,45 @@
   Periodic inform interval: 180
   Perf mgmt enable: 0
   Perf mgmt upload interval: 900
+  enable_cwmp: True
+  tac: 501
+  tac2: 501
+  # RRM / CA
+  earfcndl1: 55440
+  earfcnul1: 55440
+  earfcndl2: 55640
+  earfcnul2: 55640
+  earfcndl_list: "55440,55640"
+  earfcnul_list: "55440,55640"
+  carrier_agg_enable: False
+  carrier_number: 2
+  contiguous_cc: 0
+  prim_source: "FREE_RUNNING"
+  # RF
+  freq_band_1: 48
+  freq_band_2: 48
+  freq_band_list: "48,48"
+  tx_power: 20
+  tunnel_type: "Device.IP.Interface.1.IPv4Address.1."
 
 sas:
   sas_enabled: True
+  sas_cpi_enable: True
+  sas_manufacturer_prefix_enable: True
   sas_server_url: "https://sas.goog/v1.2/"
   sas_uid: "aether"
   sas_category: "A"
   sas_channel_type: "GAA"
   sas_cert_subject: "/C=TW/O=Sercomm/OU=WInnForum CBSD Certificate/CN=P27-SCE4255W:2009CW5000019"
   sas_location: "indoor"
-  sas_height_type: "AGL"
\ No newline at end of file
+  sas_height_type: "AGL"
+  sas_fccid: "P27-SCE4255W"
+  sas_measure_capability: "RECEIVED_POWER_WITHOUT_GRANT"
+  sas_cpi_name: "onf-cpi"
+  sas_cpi_id: "GOOG-999999"
+
+  sas_antenna_azimuth: 0
+  sas_antenna_downtilt: 0
+  sas_antenna_gain: 5
+  sas_antenna_beamwidth: 360
+  sas_cpi_signature_data: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmY2NJZCI6IlAyNy1TQ0U0MjU1VyIsImNic2RTZXJpYWxOdW1iZXIiOiJTZXJjb21tLTIwMDlDVzUwMDAwMTkiLCJpbnN0YWxsYXRpb25QYXJhbSI6eyJsYXRpdHVkZSI6MzIuMzQ0NzUyLCJsb25naXR1ZGUiOi0xMTEuMDEyMzAyLCJoZWlnaHQiOjEsImhlaWdodFR5cGUiOiJBR0wiLCJpbmRvb3JEZXBsb3ltZW50Ijp0cnVlLCJhbnRlbm5hQXppbXV0aCI6MCwiYW50ZW5uYURvd250aWx0IjowLCJhbnRlbm5hR2FpbiI6NSwiYW50ZW5uYUJlYW13aWR0aCI6MzYwLCJob3Jpem9udGFsQWNjdXJhY3kiOjMsInZlcnRpY2FsQWNjdXJhY3kiOjMsImVpcnBDYXBhYmlsaXR5IjoyOH0sInByb2Zlc3Npb25hbEluc3RhbGxlckRhdGEiOnsiY3BpSWQiOiJHT09HLTAwMTIxMiIsImNwaU5hbWUiOiJXZWktWXUgQ2hlbiIsImluc3RhbGxDZXJ0aWZpY2F0aW9uVGltZSI6IjIwMjEtMDktMDlUMDA6MDA6MDBaIn19.ljSE95LcLwKXDgrFIX43M4BUTfmkl62KQvt0TNnsZh2SUgpw0ALQCEuSzh7KHRPOvVT5F8JjsKQeeewXSrHSLXPuPwCcxwYGDrwAp_SMiXJu7-ihL-ww_qOsZ-nu1W8alMe8oyxFiEYDN0957PBr9YP-Mj8uptVJ9VKJjf1bawzx0wihwXhTtioNMmvEO_zltD83BK14kaLM0aAcstgjHjNT7tIBE-0O3QGuN8o7jdGHxy9y7FUCPSmih2B5iu2ygVALGzYglnFebK873pp3mjKPh7XO776OjkNgYHHk5uCqf4JOf03z39Cn-CQVSnZTx-1LHV-mYJnhF4yk2R_chg"
diff --git a/main.py b/main.py
index aae8e96..c34aa0d 100644
--- a/main.py
+++ b/main.py
@@ -16,6 +16,7 @@
 from unittest import mock
 
 from lte.protos.mconfig import mconfigs_pb2
+from configuration.service_configs import load_service_config
 from common.sentry import sentry_init
 from common.service import MagmaService
 from enodeb_status import (
@@ -30,6 +31,7 @@
 from rpc_servicer import EnodebdRpcServicer
 from stats_manager import StatsManager
 from tr069.server import tr069_server
+from prometheus_client import start_http_server as prometheus_start_http_server
 
 
 def get_context(ip: str):
@@ -47,6 +49,19 @@
     service = MagmaService('enodebd', mconfigs_pb2.EnodebD())
     logger.init()
 
+    enodebd_cfg = load_service_config('enodebd')
+    prometheus_cfg = enodebd_cfg.get("prometheus", None)
+
+    if not prometheus_cfg:
+        logger.warning("Prometheus configuration wasn't found in enodebd configuration.")
+    else:
+        prometheus_ip = prometheus_cfg.get("ip")
+        prometheus_port = prometheus_cfg.get("port")
+        prometheus_start_http_server(prometheus_port, addr=prometheus_ip)
+        logger.info(
+            "Starting Prometheus server on address %s:%d",
+            prometheus_ip, prometheus_port)
+
     # Optionally pipe errors to Sentry
     sentry_init(service_name=service.name)
 
diff --git a/metrics.py b/metrics.py
index 90c4b4f..877c4d9 100644
--- a/metrics.py
+++ b/metrics.py
@@ -54,6 +54,10 @@
     'enodeb_reboots',
     'ENodeB reboots by enodebd', ['cause'],
 )
+STAT_ENODEB_LAST_CONFIGURED = Gauge(
+    'enodeb_last_configured',
+    'Information of configured eNodeB', ['serial_number', 'ip_address', 'gps_lat', 'gps_lon']
+)
 
 # Metrics that are accumulated by eNodeB. Use gauges to avoid 'double-counting',
 # since eNodeB does accumulation.
diff --git a/requirements.txt b/requirements.txt
index 41d602e..97556f1 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
+wheel==0.37.1
 aiohttp==3.6.2
 aiosignal==1.2.0
 appdirs==1.4.4
@@ -40,3 +41,4 @@
 urllib3==1.26.7
 wrapt==1.13.3
 yarl==1.7.2
+cryptography==36.0.1
\ No newline at end of file
diff --git a/state_machines/enb_acs_impl.py b/state_machines/enb_acs_impl.py
index 2a7367f..4a197d4 100644
--- a/state_machines/enb_acs_impl.py
+++ b/state_machines/enb_acs_impl.py
@@ -148,6 +148,7 @@
         """ Process incoming message and maybe transition state """
         self._reset_timeout()
         msg_handled, next_state = self.state.read_msg(message)
+        logger.info("Received incoming message, transfer to new state: %s", next_state)
         if not msg_handled:
             self._transition_for_unexpected_msg(message)
             _msg_handled, next_state = self.state.read_msg(message)
@@ -157,7 +158,9 @@
     def _get_tr069_msg(self, message: Any) -> Any:
         """ Get a new message to send, and maybe transition state """
         msg_and_transition = self.state.get_msg(message)
+        logger.debug("Sending a new message to eNodeB")
         if msg_and_transition.next_state:
+            logger.info("Transfer to new state: %s", msg_and_transition.next_state)
             self.transition(msg_and_transition.next_state)
         msg = msg_and_transition.msg
         return msg
diff --git a/state_machines/enb_acs_states.py b/state_machines/enb_acs_states.py
index a9b84a5..d9e2ed9 100644
--- a/state_machines/enb_acs_states.py
+++ b/state_machines/enb_acs_states.py
@@ -10,10 +10,15 @@
 See the License for the specific language governing permissions and
 limitations under the License.
 """
+
+import time
 from abc import ABC, abstractmethod
 from collections import namedtuple
 from typing import Any, Optional
 
+import metrics
+
+from configuration.service_configs import load_service_config
 from data_models.data_model import InvalidTrParamPath
 from data_models.data_model_parameters import ParameterName
 from device_config.configuration_init import build_desired_config
@@ -964,6 +969,14 @@
                         'Status=%d' % message.Status,
                     )
             self._mark_as_configured()
+
+            metrics.STAT_ENODEB_LAST_CONFIGURED.labels(
+                serial_number=self.acs.device_cfg.get_parameter("Serial number"),
+                ip_address=self.acs.device_cfg.get_parameter("ip_address"),
+                gps_lat=self.acs.device_cfg.get_parameter("GPS lat"),
+                gps_lon=self.acs.device_cfg.get_parameter("GPS long")
+            ).set(int(time.time()))
+
             if not self.acs.are_invasive_changes_applied:
                 return AcsReadMsgResult(True, self.apply_invasive_transition)
             return AcsReadMsgResult(True, self.done_transition)
@@ -1257,6 +1270,115 @@
         return 'Waiting after eNB reboot to prevent race conditions'
 
 
+class DownloadState(EnodebAcsState):
+    """
+    The eNB handler will enter this state when firmware version is older than desired version.
+    """
+
+    def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
+        super().__init__()
+        self.acs = acs
+        self.done_transition = when_done
+
+    def get_msg(self, message: Any) -> AcsMsgAndTransition:
+
+        print("ACS Device CFG")
+        print(self.acs.device_cfg._param_to_value)
+
+        request = models.Download()
+        request.CommandKey = "20220206215200"
+        request.FileType = "1 Firmware Upgrade Image"
+        request.URL = "http://18.116.99.179/firmware/Qproject_TEST3918_2102241222.ffw"
+        request.Username = ""
+        request.Password = ""
+        request.FileSize = 57208579
+        request.TargetFileName = "Qproject_TEST3918_2102241222.ffw"
+        request.DelaySeconds = 0
+        request.SuccessURL = ""
+        request.FailureURL = ""
+        return AcsMsgAndTransition(request, self.done_transition)
+
+    def state_description(self) -> str:
+        return 'Upgrade the firmware the desired version'
+
+class WaitDownloadResponseState(EnodebAcsState):
+    """
+    The eNB handler will enter this state after the Download command sent.
+    """
+
+    def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
+        super().__init__()
+        self.acs = acs
+        self.done_transition = when_done
+        
+    def read_msg(self, message: Any) -> AcsReadMsgResult:
+        if not isinstance(message, models.DownloadResponse):
+            return AcsReadMsgResult(False, None)
+        return AcsReadMsgResult(True, None)
+
+    def get_msg(self, message: Any) -> AcsMsgAndTransition:
+        """ Reply with empty message """
+        logger.info("Received Download Response from eNodeB")
+        return AcsMsgAndTransition(models.DummyInput(), self.done_transition)
+
+    def state_description(self) -> str:
+        return "Wait DownloadResponse message"
+
+class WaitInformTransferCompleteState(EnodebAcsState):
+    """
+    The eNB handler will enter this state after firmware upgraded and rebooted
+    """
+
+    REBOOT_TIMEOUT = 300 # In seconds
+    INFORM_EVENT_CODE = "7 TRANSFER COMPLETE"
+    PREIODIC_EVENT_CODE = "2 PERIODIC"
+
+    def __init__(self, acs: EnodebAcsStateMachine, when_done: str, when_periodic: str, when_timeout: str):
+        super().__init__()
+        self.acs = acs
+        self.done_transition = when_done
+        self.periodic_update_transition = when_periodic
+        self.timeout_transition = when_timeout
+        self.timeout_timer = None
+        self.timer_handle = None
+    
+    def enter(self):
+        print("Get into the TransferComplete State")
+        self.timeout_timer = StateMachineTimer(self.REBOOT_TIMEOUT)
+
+        def check_timer() -> None:
+            if self.timeout_timer.is_done():
+                self.acs.transition(self.timeout_transition)
+                raise Tr069Error("Didn't receive Inform response after rebooting")
+
+        self.timer_handle = self.acs.event_loop.call_later(
+            self.REBOOT_TIMEOUT,
+            check_timer,
+        )
+
+    def exit(self):
+        self.timer_handle.cancel()
+        self.timeout_timer = None
+
+    def get_msg(self, message: Any) -> AcsMsgAndTransition:
+        return AcsMsgAndTransition(models.DummyInput(), None)
+
+    def read_msg(self, message: Any) -> AcsReadMsgResult:
+        if not isinstance(message, models.Inform):
+            return AcsReadMsgResult(False, None)
+        if does_inform_have_event(message, self.PREIODIC_EVENT_CODE):
+            logger.info("Receive Periodic update from enodeb")
+            return AcsReadMsgResult(True, self.periodic_update_transition)
+        if does_inform_have_event(message, self.INFORM_EVENT_CODE):
+            logger.info("Receive Transfer complete")
+            return AcsReadMsgResult(True, self.done_transition)
+
+        # Unhandled situation
+        return AcsReadMsgResult(False, None)
+
+    def state_description(self) -> str:
+        return "Wait DownloadResponse message"
+
 class ErrorState(EnodebAcsState):
     """
     The eNB handler will enter this state when an unhandled Fault is received.
@@ -1290,4 +1412,4 @@
 
     def state_description(self) -> str:
         return 'Error state - awaiting manual restart of enodebd service or ' \
-               'an Inform to be received from the eNB'
+               'an Inform to be received from the eNB'
\ No newline at end of file