0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ラズパイで部屋の環境(温度湿度気圧CO2)をモニタリングするIoTデバイスを作る④ ~Reactで可視化ダッシュボード作成~

Posted at

はじめに

この記事は下記記事の続きとなっていますので先にこちらをご覧ください。

本記事ではDynamoDBに蓄えたセンサーデータをReactApp(WEBアプリ)で可視化できるようにします。

構成

図のようにDynamoDBよりセンサーデータを取得するためにAPI Gateway、取得したデータの可視化にS3ホスティングされたReactAppを構築します。

EnvDataStack構成図V2.drawio.png

API用Lambda関数

API用Lambda関数のコードとrequirements.txtをSAMプロジェクトのapi_srcディレクトリ内に配置します。

api_src/lambda_function.py
import os
from typing import Literal
from decimal import Decimal

import boto3
from mangum import Mangum
from fastapi import FastAPI
from pydantic import BaseModel
from botocore.config import Config
from boto3.dynamodb.types import TypeDeserializer

class SensorDataItemSchema(BaseModel):
    Timestamp: str
    Value: Decimal

dynamodb_client = boto3.client(
    "dynamodb",
    config=Config(
        retries={
            "max_attempts": 5,
            "mode": "standard"
        }
    )
)
table_arn: str = os.environ["TABLE_ARN"]

api: FastAPI = FastAPI()
@api.get("/{sensor_type}/data", response_model=list[SensorDataItemSchema])
def get_sensor_data(sensor_type: Literal["concentration", "temperature","humidity","pressure"]):
    response: dict = dynamodb_client.query(**{
        "TableName": table_arn,
        "KeyConditionExpression": "#Type = :type",
        "ExpressionAttributeNames": {
            "#Type": "Type"
        },
        "ExpressionAttributeValues": {
            ":type": {"S": sensor_type}
        }
    })
    items: list[SensorDataItemSchema] = [
        SensorDataItemSchema(**{
                k: TypeDeserializer().deserialize(v)
                for k, v in item.items()
            }
        )
        for item in response.get("Items", [])
    ]
    return items

lambda_handler: Mangum = Mangum(api)

api_src/requirements.txt
annotated-types==0.7.0
anyio==4.6.2.post1
exceptiongroup==1.2.2
fastapi==0.115.5
idna==3.10
mangum==0.19.0
pydantic==2.10.2
pydantic_core==2.27.1
sniffio==1.3.1
starlette==0.41.3
typing_extensions==4.12.2

可視化ダッシュボード用ReactAppの構築

  1. create-react-appコマンドでReactAppを作成します
    >npx create-react-app env-data-viewer --template typescript
    
  2. npm installコマンドで必要なライブラリ(axios、MaterialUI関連、date-fns)をインストールします
    >npm install axios
    >npm install @mui/material @emotion/react @emotion/styled @mui/icons-material @mui/x-charts
    >npm install date-fns
    
  3. App.tsxを次のように書き換えます
    App.tsx
    import axios from 'axios';
    import { format } from 'date-fns';
    import { Grid2, Typography } from '@mui/material';
    import { useState, useEffect, type FC } from 'react';
    import { axisClasses } from '@mui/x-charts/ChartsAxis';
    import { LineChart, type ShowMarkParams } from '@mui/x-charts';
    
    interface SensorDataItemSchema {
      Timestamp: Date;
      Value: number;
    }
    
    const getSensorData = async (sensorType: string): Promise<SensorDataItemSchema[]> => {
      const url = `${process.env.REACT_APP_API_ENDPOINT}/${sensorType}/data`;
      try {
        const response = await axios.get(url);
        return response.data.map((item: {Timestamp: string, Value: string}) => ({
          Timestamp: new Date(item.Timestamp),
          Value: parseFloat(item.Value)
        }))
      } catch (error) {
        console.log("API Fail, reason:", error);
        return [];
      }
    }
    
    const dateFormatter = (value: Date) => {
      return format(value, "dd日 HH:mm");
    }
    
    const showMark = (params: ShowMarkParams) => {
      const { position } = params as ShowMarkParams<Date>;
      return [0, 6, 12, 18].includes(position.getHours()) && position.getMinutes() === 0
    };
    
    const SensorDataChart: FC<{ sensorType: string, displayName: string, unit: string}> = ({ sensorType, displayName, unit }): JSX.Element => {
      const [ sensorData, setSensorData ] = useState<SensorDataItemSchema[]>([]);
      const fetchSensorData = async () => {
        const response = await getSensorData(sensorType);
        setSensorData(response);
      }
    
      useEffect(() => {
        fetchSensorData();
      }, []);
    
      return (
        <Grid2
          size={{
            xs: 12,
            lg: 6
          }}
        >
          <Typography>
            {displayName}
          </Typography>
          <LineChart
            xAxis={[{
              data: sensorData.map((item) => (item.Timestamp)),
              scaleType: "time",
              label: "日時",
              valueFormatter: (value) => dateFormatter(value),
              tickInterval: (time) => time.getMinutes() === 0,
              tickLabelInterval: (time) => time.getMinutes() === 0
            }]}
            yAxis={[{
              label: `${displayName} (${unit})`
            }]}
            series={[{
              data: sensorData.map((item) => (item.Value)),
              valueFormatter: (value) => `${value} ${unit}`,
              showMark
            }]}
            grid={{horizontal: true}}
            height={400}
            margin={{
              left: 96
            }}
            sx={{
              [`& .${axisClasses.left} .${axisClasses.label}`]: {
                transform: 'translateX(-32px)',
              }
            }}
          />
        </Grid2>
      )
    }
    
    const App: FC = (): JSX.Element => {
      return (
        <Grid2
          container
          spacing={2}
          sx={{
            padding: 2
          }}
        >
          <SensorDataChart
            sensorType="temperature"
            displayName="温度"
            unit="℃"
          />
          <SensorDataChart
            sensorType="concentration"
            displayName="CO2濃度"
            unit="ppm"
          />
          <SensorDataChart
            sensorType="humidity"
            displayName="湿度"
            unit="%"
          />
          <SensorDataChart
            sensorType="pressure"
            displayName="気圧"
            unit="hPa"
          />
        </Grid2>
      )
    }
    
    export default App;
    

SAMによるアプリケーションのデプロイ

SAMを使用してAPI Gateway、API用Lambda関数、S3バケットを追加でデプロイします。

  1. template.yamlを次のように修正します
    template.yaml
    AWSTemplateFormatVersion: "2010-09-09"
    Transform: "AWS::Serverless-2016-10-31"
    Description: "EnvDataStack"
    
    Resources:
      EnvDataTable:  # データ格納用DynamoDBテーブル
        Type: "AWS::DynamoDB::Table"
        Properties:
          AttributeDefinitions:
            - AttributeName: "Type"
              AttributeType: "S"
            - AttributeName: "Timestamp"
              AttributeType: "S"
          BillingMode: "PROVISIONED"
          DeletionProtectionEnabled: true
          KeySchema:
            - AttributeName: "Type"
              KeyType: "HASH"
            - AttributeName: "Timestamp"
              KeyType: "RANGE"
          ProvisionedThroughput:
            ReadCapacityUnits: 1
            WriteCapacityUnits: 1
          TableName: "EnvDataTable"
          TimeToLiveSpecification:
            AttributeName: "TTL"
            Enabled: true
    
      EnvDataPusher:  # データ格納用Lambda関数
        Type: "AWS::Serverless::Function"
        Properties:
          AssumeRolePolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Principal:
                  Service:
                    - "lambda.amazonaws.com"
                Action:
                  - "sts:AssumeRole"
          AutoPublishAlias: "Alias"
          CodeUri: "src/"
          Description: "iot-env-data-collectorのデバイスシャドウ更新に応じて最新のデータをDynamoDBにプッシュする関数"
          Environment:
            Variables:
              TABLE_ARN: !GetAtt "EnvDataTable.Arn"
          Events:
            ShadowUpdateAccepted:
              Type: "IoTRule"
              Properties:
                Sql: "SELECT * FROM \"$aws/things/iot-env-data-collector/shadow/update/accepted\""
          FunctionName: "EnvDataPusher"
          Handler: "lambda_function.lambda_handler"
          MemorySize: 128
          Policies:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                Resource: "*"
              - Effect: "Allow"
                Action:
                  - "dynamodb:PutItem"
                Resource: !GetAtt "EnvDataTable.Arn"
          Runtime: "python3.9"
          Timeout: 30
    
      EnvDataViewerBucket:  # 可視化ダッシュボードS3ホスティングバケット
        Type: "AWS::S3::Bucket"
        Properties:
          BucketName: "env-data-viewer"
          PublicAccessBlockConfiguration:
            BlockPublicAcls: false
            BlockPublicPolicy: false
            IgnorePublicAcls: false
            RestrictPublicBuckets: false
          WebsiteConfiguration:
            IndexDocument: "index.html"
    
      EnvDataViewerBucketPolicy:  # バケットポリシー
        Type: "AWS::S3::BucketPolicy"
        Properties:
          Bucket: !Ref "EnvDataViewerBucket"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Action: "s3:GetObject"
                Effect: "Allow"
                Resource: !Sub
                  - "${arn}/*"
                  - arn: !GetAtt "EnvDataViewerBucket.Arn"
                Principal: "*"
    
      EnvDataHTTPAPI:  # データ取得用HTTP API
        Type: "AWS::Serverless::HttpApi"
        Properties:
          CorsConfiguration:
            AllowCredentials: false
            AllowHeaders:
              - "content-type"
            AllowMethods:
              - "*"
            AllowOrigins:
              - !GetAtt "EnvDataViewerBucket.WebsiteURL"
            MaxAge: 0
          Description: "センサーデータ取得用HTTP API"
          Name: "EnvDataHTTPAPI"
    
      EnvDataHTTPAPILambda:  # データ取得用HTTP API Lambda関数
        Type: "AWS::Serverless::Function"
        Properties:
          AssumeRolePolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Principal:
                  Service:
                    - "lambda.amazonaws.com"
                Action:
                  - "sts:AssumeRole"
          AutoPublishAlias: "Alias"
          CodeUri: "api_src/"
          Description: "センサーデータ取得用HTTP API Lambda関数"
          Environment:
            Variables:
              TABLE_ARN: !GetAtt "EnvDataTable.Arn"
          Events:
            GetSensorData:
              Type: "HttpApi"
              Properties:
                ApiId: !Ref "EnvDataHTTPAPI"
                Method: "GET"
                Path: "/{sensor_type}/data"
          FunctionName: "EnvDataHTTPAPI"
          Handler: "lambda_function.lambda_handler"
          MemorySize: 128
          Policies:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                Resource: "*"
              - Effect: "Allow"
                Action:
                  - "dynamodb:Query"
                Resource: !GetAtt "EnvDataTable.Arn"
          Runtime: "python3.9"
          Timeout: 30
    
    
  2. sam buildでデプロイの準備をします
    [EnvDataStack]$ sam build
    Starting Build use cache                                                                                                                                                                                                                                                             
    Building codeuri: /tmp/EnvDataStack/src runtime: python3.9 architecture: x86_64 functions: EnvDataPusher                                                                                                                                                                             
    Manifest file is changed (new hash: df68a8bca38f0002a68e9384d43588c6) or dependency folder (.aws-sam/deps/420c2445-345f-4edd-bd82-288634f1fd0e) is missing for (EnvDataHTTPAPILambda), downloading dependencies and copying/building source                                          
    Building codeuri: /tmp/EnvDataStack/api_src runtime: python3.9 architecture: x86_64 functions: EnvDataHTTPAPILambda                                                                                                                                                                  
    requirements.txt file not found. Continuing the build without dependencies.                                                                                                                                                                                                          
    Running PythonPipBuilder:CopySource                                                                                                                                                                                                                                                 
    Running PythonPipBuilder:CleanUp                                                                                                                                                                                                                                                    
    Running PythonPipBuilder:ResolveDependencies                                                                                                                                                                                                                                        
    Running PythonPipBuilder:CopySource                                                                                                                                                                                                                                                 
    Running PythonPipBuilder:CopySource                                                                                                                                                                                                                                                 
    
    Build Succeeded
    
    Built Artifacts  : .aws-sam/build
    Built Template   : .aws-sam/build/template.yaml
    
    Commands you can use next
    =========================
    [*] Validate SAM template: sam validate
    [*] Invoke Function: sam local invoke
    [*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch
    [*] Deploy: sam deploy --guided
    
  3. sam deployでスタックを更新します
    [EnvDataStack]$ sam deploy
    ~省略~
    Previewing CloudFormation changeset before deployment
    ======================================================
    Deploy this changeset? [y/N]: y
    ~省略~
    CloudFormation events from stack operations (refresh every 5.0 seconds)
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    ResourceStatus                                                       ResourceType                                                         LogicalResourceId                                                    ResourceStatusReason                                               
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    UPDATE_IN_PROGRESS                                                   AWS::CloudFormation::Stack                                           EnvDataStack                                                         User Initiated                                                     
    CREATE_IN_PROGRESS                                                   AWS::S3::Bucket                                                      EnvDataViewerBucket                                                  -                                                                  
    CREATE_IN_PROGRESS                                                   AWS::IAM::Role                                                       EnvDataHTTPAPILambdaRole                                             -                                                                  
    CREATE_IN_PROGRESS                                                   AWS::S3::Bucket                                                      EnvDataViewerBucket                                                  Resource creation Initiated                                        
    CREATE_IN_PROGRESS                                                   AWS::IAM::Role                                                       EnvDataHTTPAPILambdaRole                                             Resource creation Initiated                                        
    CREATE_COMPLETE                                                      AWS::S3::Bucket                                                      EnvDataViewerBucket                                                  -                                                                  
    CREATE_IN_PROGRESS                                                   AWS::S3::BucketPolicy                                                EnvDataViewerBucketPolicy                                            -                                                                  
    CREATE_IN_PROGRESS                                                   AWS::S3::BucketPolicy                                                EnvDataViewerBucketPolicy                                            Resource creation Initiated                                        
    CREATE_COMPLETE                                                      AWS::S3::BucketPolicy                                                EnvDataViewerBucketPolicy                                            -                                                                  
    CREATE_COMPLETE                                                      AWS::IAM::Role                                                       EnvDataHTTPAPILambdaRole                                             -                                                                  
    CREATE_IN_PROGRESS                                                   AWS::Lambda::Function                                                EnvDataHTTPAPILambda                                                 -                                                                  
    CREATE_IN_PROGRESS                                                   AWS::Lambda::Function                                                EnvDataHTTPAPILambda                                                 Resource creation Initiated                                        
    CREATE_COMPLETE                                                      AWS::Lambda::Function                                                EnvDataHTTPAPILambda                                                 -                                                                  
    CREATE_IN_PROGRESS                                                   AWS::Lambda::Version                                                 EnvDataHTTPAPILambdaVersion758a4c335e                                -                                                                  
    CREATE_IN_PROGRESS                                                   AWS::Lambda::Version                                                 EnvDataHTTPAPILambdaVersion758a4c335e                                Resource creation Initiated                                        
    CREATE_COMPLETE                                                      AWS::Lambda::Version                                                 EnvDataHTTPAPILambdaVersion758a4c335e                                -                                                                  
    CREATE_IN_PROGRESS                                                   AWS::Lambda::Alias                                                   EnvDataHTTPAPILambdaAliasAlias                                       -                                                                  
    CREATE_IN_PROGRESS                                                   AWS::Lambda::Alias                                                   EnvDataHTTPAPILambdaAliasAlias                                       Resource creation Initiated                                        
    CREATE_COMPLETE                                                      AWS::Lambda::Alias                                                   EnvDataHTTPAPILambdaAliasAlias                                       -                                                                  
    CREATE_IN_PROGRESS                                                   AWS::ApiGatewayV2::Api                                               EnvDataHTTPAPI                                                       -                                                                  
    CREATE_IN_PROGRESS                                                   AWS::ApiGatewayV2::Api                                               EnvDataHTTPAPI                                                       Resource creation Initiated                                        
    CREATE_COMPLETE                                                      AWS::ApiGatewayV2::Api                                               EnvDataHTTPAPI                                                       -                                                                  
    CREATE_IN_PROGRESS                                                   AWS::ApiGatewayV2::Stage                                             EnvDataHTTPAPIApiGatewayDefaultStage                                 -                                                                  
    CREATE_IN_PROGRESS                                                   AWS::Lambda::Permission                                              EnvDataHTTPAPILambdaGetSensorDataPermission                          -                                                                  
    CREATE_IN_PROGRESS                                                   AWS::Lambda::Permission                                              EnvDataHTTPAPILambdaGetSensorDataPermission                          Resource creation Initiated                                        
    CREATE_COMPLETE                                                      AWS::Lambda::Permission                                              EnvDataHTTPAPILambdaGetSensorDataPermission                          -                                                                  
    CREATE_IN_PROGRESS                                                   AWS::ApiGatewayV2::Stage                                             EnvDataHTTPAPIApiGatewayDefaultStage                                 Resource creation Initiated                                        
    CREATE_COMPLETE                                                      AWS::ApiGatewayV2::Stage                                             EnvDataHTTPAPIApiGatewayDefaultStage                                 -                                                                  
    UPDATE_COMPLETE_CLEANUP_IN_PROGRESS                                  AWS::CloudFormation::Stack                                           EnvDataStack                                                         -                                                                  
    UPDATE_COMPLETE                                                      AWS::CloudFormation::Stack                                           EnvDataStack                                                         -                                                                  
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    
    
    Successfully created/updated stack - EnvDataStack in ap-northeast-1
    

ReactAppをビルドしS3バケットに配置する

  1. ReactAppの.envファイルに作成されたAPI Gatewayのエンドポイントを記述します

    .env
    REACT_APP_API_ENDPOINT=https://8XXXXXXXXl.execute-api.ap-northeast-1.amazonaws.com
    
  2. npm run buildコマンドでReactAppをビルドします

    >npm run build
    
  3. ビルドされたbuildフォルダ内のファイル・フォルダ一式を作成されたS3バケットにアップロードします

    003.png

可視化ダッシュボードの完成

S3バケットのバケットウェブサイトエンドポイントを開くと可視化ダッシュボードが表示され、温度湿度気圧CO2のセンサーデータが時系列データとして確認することができます。

004.png

さいごに

IoT Coreに送信されたセンサーデータをDynamoDBに蓄え、ReactAppから蓄えたデータをAPIで取得しグラフィカルに表示できるようになりました。

SSL化やCognitoを利用したユーザ認証など付け加えたいものはありますが、一連の記事はここまでにすることにします。
記事中端折った部分も多分にありますが誰かのIoTデバイス作りの参考になれば幸いです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?