Skip to content

IndustryFusion/FactoryManager5.0

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FOSSA Status

Factory Manager 5.0 (For Factory Owners)

The Factory Manager 5.0 IFF application is responsible for managing the linked assets and thier data in the context of factories owned by the user. The assets created in Fleet Manager 5.0 can be imported to Factory Manager using the 'Import Assets' feature in the demo version or using IF-X dataspace manager in upcoming commercial version.

For the setup, Factory Manager 5.0 needs IFF Process Digital Twin (PDT) running on the central IFF factory server with machines connected it using individual gateways. For detailed information on setup of the factory server and gateways to deploy PDT and data agents is described here. Once the PDT is setup in the factory, the Factory Manager can be deployed on the same network to interact with the PDT semantic model and data. The Factory Manager can only manage and link the assets, the creation must be always done in Fleet Manager.

The PDT is also used in Factory Manager to create and handle Factory and ShopFloor objects. In order to enable the creation of these assets, some predefined ID store objects must be created before deploying Factory Manager.

Create these below assets. Note: In value, urn:ngsi-ld:factories:2:XXX, the XXX range is your choice. The IDs will then start from XXX+1. Also, replace the PDT URL accordingly.

curl --location 'http://<PDT-URL>/ngsi-ld/v1/entities/' \
--header 'Content-Type: application/ld+json' \
--header 'Accept: application/ld+json' \
--data-raw '{
    "@context": "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.3.jsonld",
    "id": "urn:ngsi-ld:factory-id-store",
    "type": "https://industry-fusion.org/base/v0.1/urn-holder",
    "http://www.industry-fusion.org/schema#last-urn": {
        "type": "Property",
        "value": "urn:ngsi-ld:factories:2:000"
    }
}'


curl --location 'http://<PDT-URL>/ngsi-ld/v1/entities/' \
--header 'Content-Type: application/ld+json' \
--header 'Accept: application/ld+json' \
--data-raw '{
    "@context": "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.3.jsonld",
    "id": "urn:ngsi-ld:shopFloor-id-store",
    "type": "https://industry-fusion.org/base/v0.1/urn-holder",
    "last-urn": {
        "type": "Property",
        "value": "urn:ngsi-ld:shopFloors:2:000"
    }
}'

curl --location 'http://<PDT-URL>/ngsi-ld/v1/entities/' \
--header 'Content-Type: application/ld+json' \
--header 'Accept: application/ld+json' \
--data-raw '{
    "@context": "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.3.jsonld",
    "id": "urn:ngsi-ld:global-allocated-assets-store",
    "type": "https://industry-fusion.org/base/v0.1/urn-holder",
    "http://www.industry-fusion.org/schema#last-data": {
        "type": "Relationship",
        "object": ["default"]
    }
}'


curl --location 'http://<PDT-URL>/ngsi-ld/v1/entities/' \
--header 'Content-Type: application/ld+json' \
--header 'Accept: application/ld+json' \
--data-raw '{
    "id": "urn:ngsi-ld:asset-type-store",
    "type": "https://industry-fusion.org/base/v0.1/urn-holder",
    "http://www.industry-fusion.org/schema#type-data": [
        {
            "type": "Property",
            "value": "https://industry-fusion.org/base/v0.1/filterCatridge"
        },
        {
            "type": "Property",
            "value": "https://industry-fusion.org/base/v0.1/gasController"
        },
        {
            "type": "Property",
            "value": "https://industry-fusion.org/base/v0.1/laserCutter"
        },
        {
            "type": "Property",
            "value": "https://industry-fusion.org/base/v0.1/plasmaCutter"
        },
        {
            "type": "Property",
            "value": "https://industry-fusion.org/base/v0.1/powerSource"
        },
        {
            "type": "Property",
            "value": "https://industry-fusion.org/base/v0.1/airTracker"
        },
        {
            "type": "Property",
            "value": "https://industry-fusion.org/base/v0.1/airFilter"
        },
        {
            "type": "Property",
            "value": "https://industry-fusion.org/base/v0.1/bendingMachine"
        },
        {
            "type": "Property",
            "value": "https://industry-fusion.org/base/v0.1/common"
        },
        {
            "type": "Property",
            "value": "https://industry-fusion.org/base/v0.1/deburringMachine"
        },
        {
            "type": "Property",
            "value": "https://industry-fusion.org/base/v0.1/laserCooler"
        },
        {
            "type": "Property",
            "value": "https://industry-fusion.org/base/v0.1/metalWorkpiece"
        }
    ]
}'

Aftr the above assets are created, it is also important to create custom functions in PDT Postgres to use in data dashboards. Using, Kubectl enter the acid-cluster pod in PDT. Then using below command, login to Postgres DB.

psql -U ngb -d tsdb

Then execute the following commands one by one,

CREATE OR REPLACE VIEW value_change_state_entries AS
SELECT *
FROM (
    SELECT
        -- normalize value *in place*
        CASE
            WHEN value IS NULL THEN '0'
            WHEN value ILIKE 'null' THEN '0'
            ELSE value
        END AS value,

        -- normalize prev_value *in place*
        CASE
            WHEN LAG(value) OVER (
                     PARTITION BY "entityId"
                     ORDER BY "observedAt" ASC
                 ) IS NULL
                THEN '0'
            WHEN LAG(value) OVER (
                     PARTITION BY "entityId"
                     ORDER BY "observedAt" ASC
                 ) ILIKE 'null'
                THEN '0'
            ELSE LAG(value) OVER (
                     PARTITION BY "entityId"
                     ORDER BY "observedAt" ASC
                 )
        END AS prev_value,

        -- keep all other columns untouched
        "id",
        "entityId",
        "attributeId",
        "observedAt",
        "modifiedAt",
        "datasetId",
        "nodeType",
        "attributeType",
        "valueType",
        "unitCode",
        "lang",
        "deleted"

    FROM attributes
    WHERE "attributeId" = 'https://industry-fusion.org/base/v0.1/machine_state'
) sub
WHERE value IS DISTINCT FROM prev_value;

CREATE OR REPLACE VIEW power_emission_entries_days AS
SELECT
  subquery."entityId",
  DATE_TRUNC('day', subquery.hour) AS day,
  SUM(subquery.average_power_consumption) AS total_power_consumption,
  SUM(subquery.average_power_consumption) * 0.485 AS total_carbon_emission
FROM (
  SELECT
    "entityId",
    DATE_TRUNC('hour', "observedAt") AS hour,
    AVG(
      COALESCE(
        NULLIF(REGEXP_REPLACE("value", '^[Nn][Uu][Ll][Ll]$', '0'), '')::FLOAT,
        0
      ) / 1000
    ) AS average_power_consumption
  FROM attributes
  WHERE "attributeId" = 'https://industry-fusion.org/base/v0.1/power_consumption'
  GROUP BY "entityId", DATE_TRUNC('hour', "observedAt")
) AS subquery
GROUP BY subquery."entityId", DATE_TRUNC('day', subquery.hour)
ORDER BY day;


CREATE OR REPLACE VIEW power_emission_entries_weeks AS
SELECT
  subquery."entityId",
  DATE_TRUNC('week', subquery.hour) AS week,
  SUM(subquery.average_power_consumption) AS total_power_consumption,
  SUM(subquery.average_power_consumption) * 0.485 AS total_carbon_emission
FROM (
  SELECT
    "entityId",
    DATE_TRUNC('hour', "observedAt") AS hour,
    AVG(
      COALESCE(
        NULLIF(REGEXP_REPLACE("value", '^[Nn][Uu][Ll][Ll]$', '0'), '')::FLOAT,
        0
      ) / 1000
    ) AS average_power_consumption
  FROM attributes
  WHERE "attributeId" = 'https://industry-fusion.org/base/v0.1/power_consumption'
  GROUP BY "entityId", DATE_TRUNC('hour', "observedAt")
) AS subquery
GROUP BY subquery."entityId", DATE_TRUNC('week', subquery.hour)
ORDER BY week;


CREATE OR REPLACE VIEW power_emission_entries_months AS
SELECT
  subquery."entityId",
  DATE_TRUNC('month', subquery.hour) AS month,
  SUM(subquery.average_power_consumption) AS total_power_consumption,
  SUM(subquery.average_power_consumption) * 0.485 AS total_carbon_emission
FROM (
  SELECT
    "entityId",
    DATE_TRUNC('hour', "observedAt") AS hour,
    AVG(
      COALESCE(
        NULLIF(REGEXP_REPLACE("value", '^[Nn][Uu][Ll][Ll]$', '0'), '')::FLOAT,
        0
      ) / 1000
    ) AS average_power_consumption
  FROM attributes
  WHERE "attributeId" = 'https://industry-fusion.org/base/v0.1/power_consumption'
  GROUP BY "entityId", DATE_TRUNC('hour', "observedAt")
) AS subquery
GROUP BY subquery."entityId", DATE_TRUNC('month', subquery.hour)
ORDER BY month;

CREATE OR REPLACE VIEW machine_state_daily_stats AS
WITH cleaned AS (
    SELECT
        ("observedAt" AT TIME ZONE 'UTC')::date AS day,
        CASE 
            WHEN "value" IS NULL THEN 0
            WHEN "value"::text ILIKE 'null' THEN 0
            WHEN "value" ~ '^[0-2]$' THEN "value"::int
            ELSE 0
        END AS state
    FROM attributes
    WHERE "attributeId" = 'https://industry-fusion.org/base/v0.1/machine_state'
      AND "observedAt" >= now() - INTERVAL '10 days'
),
counts AS (
    SELECT
        day,
        COUNT(*) AS total,
        COUNT(*) FILTER (WHERE state = 0) AS count_0,
        COUNT(*) FILTER (WHERE state = 1) AS count_1,
        COUNT(*) FILTER (WHERE state = 2) AS count_2
    FROM cleaned
    GROUP BY day
),
percentages AS (
    SELECT
        to_char(day, 'DD.MM.YYYY') AS date,
        round((count_0::decimal / NULLIF(total,0)) * 100, 2) AS pct_0,
        round((count_1::decimal / NULLIF(total,0)) * 100, 2) AS pct_1,
        round((count_2::decimal / NULLIF(total,0)) * 100, 2) AS pct_2,

        round((count_0::decimal / NULLIF(total, 0)) * 24, 2) AS hours_0,
        round((count_1::decimal / NULLIF(total, 0)) * 24, 2) AS hours_1,
        round((count_2::decimal / NULLIF(total, 0)) * 24, 2) AS hours_2,

        day   -- keep for sorting, not exposed in final select
    FROM counts
)
SELECT
    date,
    pct_0, pct_1, pct_2,
    hours_0, hours_1, hours_2
FROM percentages
ORDER BY day DESC;

CREATE OR REPLACE VIEW machine_state_2h_stats AS
WITH cleaned AS (
    SELECT
        ("observedAt" AT TIME ZONE 'UTC')::date AS day,
        EXTRACT(HOUR FROM ("observedAt" AT TIME ZONE 'UTC')) AS time,
        CASE 
            WHEN "value" IS NULL THEN 0
            WHEN "value"::text ILIKE 'null' THEN 0
            WHEN "value" ~ '^[0-2]$' THEN "value"::int
            ELSE 0
        END AS state
    FROM attributes
    WHERE "attributeId" = 'https://industry-fusion.org/base/v0.1/machine_state'
      AND "observedAt" >= now() - INTERVAL '10 days'
),
intervals AS (
    SELECT
        day,
        FLOOR(time / 2)::int AS interval_index,
        COUNT(*) AS total,
        COUNT(*) FILTER (WHERE state = 0) AS count_0,
        COUNT(*) FILTER (WHERE state = 1) AS count_1,
        COUNT(*) FILTER (WHERE state = 2) AS count_2
    FROM cleaned
    GROUP BY day, interval_index
),
formatted AS (
    SELECT
        day,
        to_char(day, 'DD.MM.YYYY') AS date,
        interval_index,

        -- Label only end of the interval: 02:00, 04:00, ...
        LPAD(((interval_index * 2) + 2)::text, 2, '0') || ':00'
        AS hour_mark,

        round((count_0::decimal / NULLIF(total,0)) * 100, 2) AS pct_0,
        round((count_1::decimal / NULLIF(total,0)) * 100, 2) AS pct_1,
        round((count_2::decimal / NULLIF(total,0)) * 100, 2) AS pct_2,

        round((count_0::decimal / NULLIF(total, 0)) * 2, 2) AS hours_0,
        round((count_1::decimal / NULLIF(total, 0)) * 2, 2) AS hours_1,
        round((count_2::decimal / NULLIF(total, 0)) * 2, 2) AS hours_2
    FROM intervals
)
SELECT
    date,
    hour_mark AS "time",
    pct_0, pct_1, pct_2,
    hours_0, hours_1, hours_2
FROM formatted
ORDER BY day DESC, interval_index ASC;

GRANT SELECT ON value_change_state_entries TO pgrest;

GRANT SELECT ON power_emission_entries_days TO pgrest;

GRANT SELECT ON power_emission_entries_weeks TO pgrest;

GRANT SELECT ON power_emission_entries_months TO pgrest;

GRANT SELECT ON machine_state_daily_stats TO pgrest;

GRANT SELECT ON machine_state_2h_stats TO pgrest;

After creation, close the pod console and refresh the timescale bridge. For more information, use this document.

The application also uses S3 as object storage, MongoDB for UI object storage and redis as cache storage. Create a demo S3 bucket in your favourite cloud provider, deploy redis using this or using Docker and deploy MongoDB instance using Docker or Community Mongo Operator link. Then feed the details in .env of backend folder together with PDT endpoint information as shown below.

Exmaple .env of backend root folder:


GITHUB_BASE_URL=https://api.github.com/repos/<owner>/<repo>/contents
GITHUB_TOKEN=<git token for above repo>
API_URL=http://<PDT-URL>/auth/realms/iff/protocol/openid-connect/token
CLIENT_ID=scorpio
SCORPIO_URL=http://<PDT-URL>/scorpio/ngsi-ld/v1/entities
S3_URL=<S3 URL>
S3_ACCESS_KEY=<S3 Access Key>
S3_SECRET_KEY=<S3 Secret Key>
S3_BUCKET=<S3 Bukect Name>
ALERTA_URL=http://<PDT-URL>/alerta/api
ALERTA_KEY=<Alerta Key>
TIMESCALE_URL=http://<PDT-URL>/pgrest/
MONGO_URL=mongodb://<username>:<password>@<hostname or IP>:<port>/<DB_Name>?directconnection=true&retryWrites=true&w=majority
CORS_ORIGIN=http://localhost:3002
REDIS_SERVER=<hostname or IP>
REDIS_PORT=6379

Once the .env is added to the code, install dependencies in 'backend' and 'frontend' projects using,

npm install

And then run the backend project using,

npm run start

And then run the frontend project using,

npm run dev

The UI application will be available at localhost:3002.

Copyrights: IB Systems GmbH.

About

Open-Source Edge version of Factory Manager.

Topics

Resources

License

Stars

Watchers

Forks

Contributors 8

Languages