家庭燃气表微信抄表系统

1.设计目标

  • 需求:家里燃气度数的读数上报,每个月需在社区微信群里将手机拍摄的燃气表读数截图(加住址信息水印),发到群里给抄表员
  • 目标:手机上随时可以远程采集读数图片(自动加住址信息水印),在微信上发送给社区抄表员。

2.总体设计

在这里插入图片描述

在这里插入图片描述

3.燃气抄表采集端

使用树莓派(两点理由,第一支持Type-C供电且低功耗,可以加移动电源长期运行,第二支持GPIO,以后加传感器有扩展空间),部署Docker环境。
https://www.raspberrypi.com/software/
在这里插入图片描述
使用树莓派官网的系统安装工具完成系统安装,安装完成之后部属宝塔BT系统进行系统管理。
https://www.bt.cn/new/download.html
在这里插入图片描述

3.1 功能描述

接收管理服务器实时采集请求,实现燃气表拍照的采集上报,摄像头采集之前要自动打开小夜灯照明,厨柜内较黑拍不清楚。

3.2 容器配置

3.2.1 Dockerfile-GAS-ARM文件

FROM ubuntu:latest
ENV LANG C.UTF-8
ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update
RUN apt install -y openssh-client openssh-server
RUN apt-get install -y wget curl git telnet vim make gcc 
RUN apt-get install -y python3 python3-pip libpcre3 libpcre3-dev
RUN apt-get install -y python3-opencv

RUN apt clean \
    && rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp* \
RUN apt autoremove

WORKDIR /tmp
RUN apt-get update
RUN apt-get upgrade -y
RUN apt install -y libusb-1.0-0-dev
RUN git clone https://github.com/mvp/uhubctl
WORKDIR /tmp/uhubctl
RUN make && make install

# Install opencv-python
RUN pip3 install --upgrade pip -i https://pypi.mirrors.ustc.edu.cn/simple/
RUN pip3 install paho-mqtt pytest-shutil requests -i https://pypi.mirrors.ustc.edu.cn/simple/ 

WORKDIR /home
CMD ["/bin/bash"]

3.2.2 docker-compose.yaml文件

version: "3.3"
services:
  #燃气采集设备端
  #docker exec -it gas_meter_reading_t /bin/bash
  gas_meter_reading_t:
    build:
      context: ./
      dockerfile: Dockerfile-GAS-ARM
    image: gas_meter_reading:v1
    container_name: gas_meter_reading_t
    working_dir: /workspace
    devices:
      - /dev/video0:/dev/video0
    volumes:
      - ./workspace:/workspace
    restart: always
    tty: true
    privileged: true

3.3 采集代码

在这里插入图片描述

#!/usr/bin/python3
import base64
import asyncio
import paho.mqtt.client as mqtt
import cv2
import subprocess
import datetime
import threading
import time
import json
import shutil

class GMRDevice:
    def __init__(self, cam_simulate=False):
        self.mqtt_server_ip = '58.212.21.66'
        self.usbhub  = '1'
        self.hubport = '1'
        self.client = mqtt.Client()
        self.cam_simulate = cam_simulate

    def get_img_path(self):
        now = datetime.datetime.now()
        img_name = "{:04d}{:02d}{:02d}{:02d}{:02d}{:02d}".format(
            now.year, 
            now.month, 
            now.day, 
            now.hour, 
            now.minute, 
            now.second
            )
        print("当前时间:{}".format(img_name))
        return './images/' + img_name + '.jpg'

    #夜灯开关
    def set_led(self, cmd):
        try:
            output = subprocess.check_output(
            ["uhubctl", "-l", self.usbhub, "-p", self.hubport, "-a", cmd], 
            stderr=subprocess.STDOUT)
            print(output.decode("utf-8"))
        except subprocess.CalledProcessError as e:
            print("命令执行失败:", e.output.decode("utf-8"))

    #摄像头拍照
    def gas_meter_photography(self, imgPath):
        if self.cam_simulate == True:
            print('gas_meter_photography simulate(true)')
            shutil.copyfile('./''simulate''.jpg', imgPath)
            return True

        print('gas_meter_photography simulate(false)')
        self.set_led('on')
        time.sleep(1)
        isOK = False
        cap = cv2.VideoCapture(0)
        if cap.isOpened():
            print('宽:', cap.get(cv2.CAP_PROP_FRAME_WIDTH) )
            print('高:', cap.get(cv2.CAP_PROP_FRAME_HEIGHT) )
            print('帧率:', cap.get(cv2.CAP_PROP_FPS) )
            print('亮度:', cap.get(cv2.CAP_PROP_BRIGHTNESS) )
            print('对比度:', cap.get(cv2.CAP_PROP_CONTRAST) )
            print('饱和度:', cap.get(cv2.CAP_PROP_SATURATION) )
            print('色调:', cap.get(cv2.CAP_PROP_HUE) )
            print('曝光度:', cap.get(cv2.CAP_PROP_EXPOSURE) )
            #cap.set(cv2.CAP_PROP_CONTRAST, 100)
            #cap.set(cv2.CAP_PROP_EXPOSURE, 200)
            #cap.set(cv2.CAP_PROP_BRIGHTNESS, 100)
            
            __, frame = cap.read()
            farm = cv2.resize(frame, dsize = (1600, 1200) )
            cv2.imwrite(imgPath,farm)
            isOK = True
        cap.release()
        time.sleep(1)
        self.set_led('off')
        return isOK

    #上报抄表照片
    def send_photo(self, imgPath):
        if not self.client.is_connected(): 
            return

        with open(imgPath, 'rb') as f:
            image_data = f.read()
            image_base64 = base64.b64encode(image_data)
            self.client.publish('gmrdev/fixedtime', image_base64)

    def on_connect(self, client, userdata, flags, rc):
        print("Connection returned " + str(rc))
    
    #实时上报
    def on_message(self, client, userdata, message):
        print(message.topic)
        print("Message Recieved: " + message.payload.decode())
        
        if message.topic == 'gmrplat/nowtime':
            imgPath = self.get_img_path()
            self.gas_meter_photography(imgPath)
            self.send_photo(imgPath)

    def on_publish(self, mqttc, obj, mid):
        print("mid: " + str(mid))
    
    def on_subscribe(self, mqttc, obj, mid, granted_qos):
        print("Subscribed: " + str(mid) + " " + str(granted_qos))
    
    def on_log(self, mqttc, obj, level, string):
        print(string)
    
    def on_disconnect(self, mqttc, obj, rc):
        print("unsuccess connect %s" % rc)
    
    def loop_forever(self):
        self.client.connect(self.mqtt_server_ip, 1883)
        self.client.on_message    = self.on_message
        self.client.on_connect    = self.on_connect
        self.client.on_publish    = self.on_publish
        self.client.on_subscribe  = self.on_subscribe
        self.client.on_log        = self.on_log
        self.client.on_disconnect = self.on_disconnect
        self.client.subscribe('gmrplat/nowtime')
        self.client.loop_forever()
    
def mqtt_task(grmdev):
    print("子线程%s开始..."%(threading.current_thread().name))
    grmdev.loop_forever()
    print("子线程%s结束..."%(threading.current_thread().name))

#每天自动采集一次,设计采集1年的量    
def gmr_task(grmdev):
    time.sleep(5)
    for x in range(1, 365):
        imgPath = grmdev.get_img_path()
        grmdev.gas_meter_photography(imgPath)
        grmdev.send_photo(imgPath)
        time.sleep(60 * 60 * 24)
    print('');

if __name__ == '__main__':
    gmrdev = GMRDevice()
    #gmrdev = GMRDevice(cam_simulate=True)

    print("主线程%s启动..."%(threading.current_thread().name))
    t1 = threading.Thread(target = mqtt_task, args=(gmrdev,))
    t1.start()
    
    gmr_task(gmrdev)
    t1.join()
    print("主线程%s结束..."%(threading.current_thread().name))

4.燃气表抄表平台

系统环境要求:LINUX系统,支持Docker环境。

4.1 容器配置

4.1.1 Dockerfile-GMR文件

FROM sapk/cloud9

COPY sources.list /etc/apt/sources.list

RUN apt update
RUN apt upgrade -y
RUN apt install -y wget python3 curl inetutils-ping graphicsmagick vim

WORKDIR /tmp
#download font(AlibabaPuHuiTi-3-65-Medium.ttf)
RUN apt install -y unzip
RUN wget https://puhuiti.oss-cn-hangzhou.aliyuncs.com/AlibabaPuHuiTi-3/AlibabaPuHuiTi-3-65-Medium.zip
RUN unzip AlibabaPuHuiTi-3-65-Medium.zip && rm AlibabaPuHuiTi-3-65-Medium.zip && rm -r __MACOSX
RUN cp /tmp/AlibabaPuHuiTi-3-65-Medium/AlibabaPuHuiTi-3-65-Medium.ttf /tmp && rm -r /tmp/AlibabaPuHuiTi-3-65-Medium

# Install nvm node 16
RUN apt install -y git
RUN git clone http://github.com/creationix/nvm.git /root/.nvm;
RUN chmod -R 777 /root/.nvm/;
RUN bash /root/.nvm/install.sh;
RUN export NVM_DIR="$HOME/.nvm";
RUN echo "[[ -s $HOME/.nvm/nvm.sh ]] && . $HOME/.nvm/nvm.sh" >> $HOME/.bashrc;

RUN bash -i -c 'nvm install 16'
RUN bash -i -c 'npm install -g pnpm pm2'

WORKDIR /workspace

CMD ["/bin/bash"]

4.1.2 Dockerfile_OCR文件

FROM ubuntu
RUN apt update
RUN apt upgrade -y
RUN apt install -y wget libgl1 libglib2.0-dev python3 pip
RUN pip3 install --upgrade pip -i https://mirror.baidu.com/pypi/simple

#安装百度PaddleOCR
RUN pip3 install requests paddlepaddle paddleocr -i https://mirror.baidu.com/pypi/simple
RUN wget http://nz2.archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.19_amd64.deb
RUN dpkg -i libssl1.1_1.1.1f-1ubuntu2.19_amd64.deb

CMD ["/bin/bash"]

4.1.3 docker-compose.yaml文件

version: '3.3'
services:
  #Gas meter reading platform:燃气采集平台
  #docker exec -it gmrplat_t /bin/bash
  gmrplat_t:
    hostname: gmrplat
    build:
      context: ./
      dockerfile: Dockerfile-GMR
    working_dir: /workspace
    image: gmrplat:v1
    volumes:
      - ./workspace:/workspace
    container_name: gmrplat_t
    ports:
      - 8484:8181
      - 8477:8477
      - 8488:8488
    restart: always
    command: --port 8181 --auth admin:cw    
    tty: true
    networks:
      - gmrnet

  #百度AI/图像识别
  #docker exec -it paddle_ocr_t /bin/bash  
  paddle_ocr_t:
    hostname: ocr
    container_name: paddle_ocr_t
    build:
      context: ./
      dockerfile: Dockerfile-OCR
    image: gmr_paddle_ocr
    volumes:
      - ./PaddleOCR:/PaddleOCR
    tty: true
    privileged: true
    restart: always
    command: /bin/bash
    networks:
      - gmrnet
    
  #开源物联网MQTT服务器(https://www.emqx.com/)
  #初始用户名密码:admin/public
  #docker exec -it emq_t /bin/bash
  emq_t:
    hostname: emq
    container_name: emq_t
    image: emqx/emqx:5.3.0
    ports:
      - 1883:1883
      - 8083:8083
      - 8084:8084
      - 8883:8883
      - 18083:18083
    restart: always
    tty: true
    networks:
      - gmrnet
  
  #开源数据库,兼容MYSQL
  mariadb_t:
    hostname: mariadb
    container_name: mariadb_t
    image: mariadb
    environment:
     - MYSQL_ROOT_PASSWORD=cjy
     - MYSQL_DATABASE=gmrplat
     - MYSQL_USER=cjy
     - MYSQL_PASSWORD=cjy
    restart: always
    networks: 
     - gmrnet
     
  #数据库WEB后台管理
  phpmyadmin_t:
    hostname: phpmyadmin
    container_name: phpmyadmin_t
    image: phpmyadmin:5.2-apache
    ports:
     - 4090:80
    environment:
     - PMA_ARBITRARY=1
     - PMA_HOST=mariadb
     - PMA_PORT=3306
     - PMA_USER=root
     - PMA_PASSWORD=cjy
    restart: always
    networks: 
     - gmrnet

#自定义容器网络
networks:
  gmrnet:
    ipam:
      driver: default
      config:
        - subnet: 172.77.1.0/24

4.2 图像识别

百度开源AI:https://github.com/PaddlePaddle/PaddleOCR
在这里插入图片描述照片OCR识别样例代码

from paddleocr import PaddleOCR, draw_ocr
ocr = PaddleOCR(use_angle_cls=True, lang="en")
img_path = 'xx.jpg'
result = ocr.ocr(img_path, cls=True)
for idx in range(len(result)):
    res = result[idx]
    for line in res:
        print(line)

4.3 微信前台

主要代码。

<script setup>
import { ref } from 'vue'
import 'weui';
import axios from 'axios';

async function get_gmr_img() {
    console.log('get_gmr_img')
    try{
        let res = await axios.get('/api/gmr_img_now');
        console.log(res);
        let result = res.data;
        console.log(result);

        res = await axios.get('/api/gmr_get_last_img');
        console.log(res);
        result = res.data[0];
        console.log(result.img_name);
        const imgUrl = '/images/mask_' + result.img_name;
        document.getElementById('gmr_img').src = imgUrl
    }catch{
        console.log('请求失败');
    }
}
</script>

<template>
    <img id="gmr_img" src="/pi.jpg" class=gmr_img>
    <button class="weui-btn weui-btn_primary" @click="get_gmr_img">立即更新</button>
    <div style="text-align:center;" id="actionsheet1" class="weui_actionsheet_cell">长按燃气表图片2秒转发给抄表员</div>
</template>

<style scoped>
.gmr_img {
    width:  100vw;
    height: 80vh;
}
</style>

4.4 采集后台

CREATE TABLE `gmrinfo` (
  `id` bigint(12) NOT NULL,
  `gather_time` datetime NOT NULL DEFAULT current_timestamp(),
  `img_name` varchar(30) NOT NULL,
  `img_ocr` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

ALTER TABLE `gmrinfo`
  ADD PRIMARY KEY (`id`);

ALTER TABLE `gmrinfo`
  MODIFY `id` bigint(12) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=12;
COMMIT;
const {promises:fs} = require("fs");
const moment = require('moment');
const Koa    = require('koa');
const Router = require('koa-router');
const koa_static  = require('koa-static');
const mqtt   = require('mqtt');
const axios  = require('axios');
const knex   = require('knex'); //https://knexjs.org/
const gm     = require('gm');

const OCR_SERVER_IP    = 'ocr'
const OCR_SERVER_PORT  = 8466

const MQTT_SERVER_IP   = 'emq'
const MQTT_SERVER_PORT = 1883

const DB_SERVER_IP     = 'mariadb'
const DB_USER          = 'cjy'
const DB_PWD           = 'cjy'
const DB_NAME          = 'gmrplat'

const GMRPLAT_SERVER_PORT = 8488

const db = knex({
  client: "mysql2",
  connection: {
    host: DB_SERVER_IP,
    port: 3306,
    user: DB_USER,
    password: DB_PWD,
    database: DB_NAME,
  },
});

const WWWROOT = __dirname + '/public'
const IMGDIR  = WWWROOT + '/images/'

//图像识别
const GMRToOCR = async (imgfile) => {
    const url = "http://" + OCR_SERVER_IP + ":" + OCR_SERVER_PORT + "/GMROCR"
    const bitmap = await fs.readFile(imgfile);
    const adata = {
        request_id: '001',
        img_base64: ''
    }
    adata['img_base64'] = Buffer.from(bitmap).toString('base64');
    const res = await axios.post(url, adata);
    console.log(res.data)
    console.log(res.data.ocr_res)
    return res.data
}

const client = mqtt.connect("mqtt://" + MQTT_SERVER_IP + ":" + MQTT_SERVER_PORT);
client.subscribe("gmrdev/fixedtime")

client.on('message', async (topic, payload) => {
    console.log(`接收MQTT订阅消息:${topic}`);

    const base64EncodedfileData = payload.toString();
    const fileDataDecoded = Buffer.from(base64EncodedfileData,'base64');
    const img_name = moment(Date.now()).format('YYYYMMDDHHmmss');
    fs.writeFile(IMGDIR + img_name + ".jpg", fileDataDecoded, err => {})

    gm(IMGDIR + img_name + ".jpg")
      .stroke("blue") //字体外围颜色
      .fill("blue")   //字体内围颜色(不设置默认为黑色)
      .font("/tmp/AlibabaPuHuiTi-3-65-Medium.ttf", 60) //字库所在文件夹和字体大小
      .drawText(50,50, img_name + "\nXX街道X小区X幢X室")
      .write(IMGDIR + "mask_" + img_name + ".jpg", async (err) => {
         if (!err){
            console.log(`${img_name}:加水印成功`);
            await db('gmrinfo').insert({img_name: `${img_name}.jpg`})
            console.log(`${img_name}:写入数据库`);
            //const ocr_res = await GMRToOCR(IMGDIR + img_name + ".jpg");
         }
         else {
            console.log(err);
         }
    });
});

const app_http  = new Koa()
//允许跨域
app_http.use(async (ctx, next) =>{
    await next();
    ctx.set("Access-Control-Allow-Origin", "*");
    ctx.set("Access-Control-Allow-Headers", "Content-Type");
    ctx.set("Access-Control-Allow-Methods", "*");
});

//静态文件
app_http.use(koa_static(WWWROOT, {index: 'index.html', hidden: false, defer: true}))

let lastupdate = true
const sleep = async (ms) => {
    return new Promise(resolve=>setTimeout(resolve, ms))
}

//收到微信上H5的请求后,通知采集设备拍照燃气表上传
const gmr_img_now = async (ctx) => {
    console.log('微信H5请求:通知采集设备拍照燃气表上传')
    client.publish('gmrplat/nowtime', 'now')
    ctx.body = '{errorcode:0}'
    lastupdate = false
}

//收到微信上H5的请求后,返回最新的燃气表拍照
const gmr_get_last_img = async (ctx) => {
    console.log('微信H5请求:获取最新燃气表拍照')
    for(x = 0; x < 10; x++){
        if(lastupdate == false){
            lastupdate = true
            break
        }
        await sleep(1)
    }
    const result = await db("gmrinfo").select(["id", "gather_time","img_name","img_ocr"]).orderBy('id', 'desc').limit(1)
    console.log(result)
    ctx.body = result
}

const router  = new Router();
router.get('/api/gmr_img_now', gmr_img_now);
router.get('/api/gmr_get_last_img', gmr_get_last_img);
app_http.use(router.routes());

app_http.listen(GMRPLAT_SERVER_PORT, '0.0.0.0', async () => {
    //获取公网IP地址
    let publicip = 'x.x.x.x'
    const res = await axios('http://ifconfig.co/json')
    publicip = res.data.ip.trim()
    console.log(`服务启动:http://${publicip}:${GMRPLAT_SERVER_PORT}`);
})

5.系统演示

配套代码
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值