前言:

It has been a while since I last updated my blogs.

使用Tornado开发一个实时监控信息系统,其中包括 CUP、内存、网卡、磁盘使用率。

涉及技术

编程语言:Python

关系型数据库:MySQL

Web框架:Tornado

数据库连接驱动:mysql-connector-python

数据库ORM:sqlalchemy

服务端websocket通信:sockjs-tornado

客户端websocket通信:sockejs-client

前端:HTML、CSS、JS

图表可视化:pyecharts==0.5.11 #新版本和旧版本差异较大

获取硬件信息工具:psutil模块

pip install -i https://pypi.douban.com/simple --trusted-host pypi.douban.com -r requirements.txt
-i:指定国内安装源 --trusted-host:指定信任主机 -r指定依赖文件

Tornado项目目录结构设计

Monitor

-------->app01

------>models

     ------>static

   ------->template

   ------->tools

   ------->views

manage.py

from sqlalchemy.ext.declarative import declarative_base #模型继承的父类
from sqlalchemy.dialects.mssql import BIGINT,DECIMAL,DATE,TIME,DATETIME#导入数据库字段
from sqlalchemy import Column#用于创建字段的类 Base=declarative_base()#调用
metadata=Base.metadata#创建元类 class Mem(Base):
__tablename__='mem'
id=Column(BIGINT,primary_key=True)
precent=Column(DECIMAL(6,2))#小数类型 保留6位数字 2位小数
total=Column(DECIMAL(8,2))#总量
used=Column(DECIMAL(8,2))#使用率
free=Column(DECIMAL(8,2))#剩余率
create_date=Column(DATE)#创建的日期
create_time=Column(TIME)#c创建的时间
create_dt=Column(DATETIME)#创建的日期+时间 class Swap(Base):
__tablename__='swap'
id = Column(BIGINT,primary_key=True)
precent = Column(DECIMAL(6,2)) # 小数类型 保留6位数字 2位小数
total = Column(DECIMAL(8,2)) # 总量
used = Column(DECIMAL(8,2)) # 使用率
free = Column(DECIMAL(8,2)) # 剩余率
create_date = Column(DATE) # 创建的日期
create_time = Column(TIME) # c创建的时间
create_dt = Column(DATETIME) # 创建的日期+时间 class CPU(Base):
__tablename__ = 'cpu'
id = Column(BIGINT, primary_key=True)
precent = Column(DECIMAL(6, 2)) # 小数类型 保留6位数字 2位小数
create_date = Column(DATE) # 创建的日期
create_time = Column(TIME) # c创建的时间
create_dt = Column(DATETIME) # 创建的日期+时间 if __name__ == '__main__':
from sqlalchemy import create_engine
mysql_configs={
"db_host":'192.168.56.128',
"db_name":"web",
"db_port":3306,
"db_user":"web",
"db_pwd":"123.com"
}
link="mysql+mysqlconnector://{db_user}:{db_pwd}@{db_host}:{db_port}/{db_name}".format(**mysql_configs)
engine=create_engine(link,encoding="utf-8",echo=True)
metadata.create_all(engine)#调用元类

models.py

一、前端(SockJS)与Tornado建立web socket连接

前端使用:sockejs-client插件

//1.定义长连接
var conn = null; //2.定义连接函数 function connect() { disconnect();//把之前的连接关闭掉,在创建新的连接
//0.定义协议
var transports = ["websocket"];
//1.创建连接对象
conn = new SockJS("http://" + window.location.host + "/real/time",transports); //2.建立连接
conn.onopen = function () {
console.log('连接成功')
};
//3.建立发送消息
conn.onmessage = function (e) {
console.log(e.data);
};
//4.建立关闭连接
conn.onclose = function (e) {
console.log("断开连接");
};
setInterval(function () {
conn.send("system")
},1000)
} function disconnect() {
if (conn != null) {
conn.close();
conn = null;
} } if (conn == null) {
connect(); } else {
disconnect();
}

monitor.js

Tornado使用:sockjs

from sockjs.tornado import SockJSConnection#专门生成web_socket服务

class RealTimeHandler(SockJSConnection):
waiters=set()#定义1个客户端连接池,所有客户端共用1个集合
#1.建立连接(不存在重复的连接)
def on_open(self, request):
try:
self.waiters.add(self)
except Exception as e:
print(e)
#2.发送消息
def on_message(self, message):#接收客户端消息
try:
self.broadcast(self.waiters,message)#把消息广播给所有连接的客户端
except Exception as e:
print(e)
#3.关闭连接
def on_close(self):
try:
self.waiters.remove(self)
except Exception as e:
print(e)

views_real_time.py

二、前端数据实时更新

1.CPU信息实时更新(水球图)

Tornado调用pyecharts生成前端代码(html+js+css);

import datetime
from pyecharts import Liquid, Gauge, Pie, Line #水球图、 class Chart(object):
def liquid_html(self,chart_id,title,val):#水球图
liquid = Liquid(
title="{}-{}".format(self.dt, title),
title_pos="center",
width="100%",
title_color="white",
title_text_size=14,
height=300
)
liquid.chart_id=chart_id#指定 chart_id
liquid.add("",[round(val/100,4)])#添加数据
return liquid.render_embed()#返回html图表代码 @property
def dt(self):
return datetime.datetime.now().strftime('%Y%m%dT%H:%M:%S')

chart.py

Tornado把pyecharts生成的前端代码(html+js+css),响应给浏览器渲染;

<div id="cpu_avg" style="width:100%;height:300px;"></div>
<script type="text/javascript"> var myChart_cpu_avg = echarts.init(document.getElementById('cpu_avg'), 'light', {renderer: 'canvas'}); var option_cpu_avg = {
"title": [
{
"text": "20190910T15:13:19-cpu\u5e73\u5747\u4f7f\u7528\u7387",
"left": "center",
"top": "auto",
"textStyle": {
"color": "white",
"fontSize": 14
},
"subtextStyle": {
"fontSize": 12
}
}
],
"toolbox": {
"show": true,
"orient": "vertical",
"left": "95%",
"top": "center",
"feature": {
"saveAsImage": {
"show": true,
"title": "save as image"
},
"restore": {
"show": true,
"title": "restore"
},
"dataView": {
"show": true,
"title": "data view"
}
}
},
"series_id": 8147274,
"tooltip": {
"trigger": "item",
"triggerOn": "mousemove|click",
"axisPointer": {
"type": "line"
},
"textStyle": {
"fontSize": 14
},
"backgroundColor": "rgba(50,50,50,0.7)",
"borderColor": "#333",
"borderWidth": 0
},
"series": [
{
"type": "liquidFill",
"data": [
0.167
],
"waveAnimation": true,
"animationDuration": 2000,
"animationDurationUpdate": 1000,
"color": [
"#294D99",
"#156ACF",
"#1598ED",
"#45BDFF"
],
"shape": "circle",
"outline": {
"show": true
}
}
],
"legend": [
{
"data": [],
"selectedMode": "multiple",
"show": true,
"left": "center",
"top": "top",
"orient": "horizontal",
"textStyle": {
"fontSize": 12
}
}
],
"animation": true,
"color": [
"#c23531",
"#2f4554",
"#61a0a8",
"#d48265",
"#749f83",
"#ca8622",
"#bda29a",
"#6e7074",
"#546570",
"#c4ccd3",
"#f05b72",
"#ef5b9c",
"#f47920",
"#905a3d",
"#fab27b",
"#2a5caa",
"#444693",
"#726930",
"#b2d235",
"#6d8346",
"#ac6767",
"#1d953f",
"#6950a1",
"#918597",
"#f6f5ec"
]
};
myChart_cpu_avg.setOption(option_cpu_avg); </script>
</div>

tornado调用pyechart生成的前端代码

浏览器发送web socket请求给Tornado server端

Tornado不断响应浏览器pyecharts生成前端代码(html+js+css)

import json
from sockjs.tornado import SockJSConnection#专门生成web_socket服务
from app01.tools.monitor_tools import Monitor
class RealTimeHandler(SockJSConnection):
waiters=set()#定义1个客户端连接池,所有客户端共用1个集合
#1.建立连接(不存在重复的连接)
def on_open(self, request):
try:
self.waiters.add(self)
except Exception as e:
print(e)
#2.发送消息
def on_message(self, message):#接收客户端消息,根据客户端发送过来的消息返回一些数据!
try:
if message =="system":
m=Monitor()
data={"mem":m.mem(),"swap":m.swap(),"cpu":m.cpu(),"disk":m.disk(),"net":m.net(),'dt':m.dt}
self.broadcast(self.waiters,json.dumps(data,ensure_ascii=False))#把消息广播给所有连接的客户端
except Exception as e:
print(e)
#3.关闭连接
def on_close(self):
try:
self.waiters.remove(self)
except Exception as e:
print(e)

views_real_time.py

浏览器通过JS代码不断修改pyechart生成的前端代码

//进度条变化
function progress_status(val) {
var data = "";
if (val >= 0 && val < 25) {
data = " bg-success";
} else if (val >= 25 && val < 50) {
data = "";
} else if (val >= 50 && val < 75) {
data = " bg-warning";
} else if (val >= 75 && val <= 100) {
data = " bg-success";
}
return data
} function update_ui(e) {
var data=JSON.parse(e.data);
//因为pyechart 声明了变量option_cpu_avg所有我们只需要修改现有的变量option_cpu_avg即可
option_cpu_avg.series[0].data[0]=(data['cpu']['percent_avg']/100).toFixed(4); //保留4位小数
option_cpu_avg.title[0].text=data['dt']+'CPU平均使用率';
//保证对option_cpu_avg的修改生效
myChart_cpu_avg.setOption(option_cpu_avg);
//-------------------------------------------------------
var cpu_per = "";
for (var k in data['cpu']['percent_per']) {
var num = parseInt(k);
cpu_per += "<tr><td class='text-primary' style='width: 30%'>CPU" + num + "</td>";
cpu_per += "<td><div class='progress'><div class='progress-bar progress-bar-striped progress-bar-animated" + progress_status(data['cpu']['percent_per'][k]) + "' role='progressbar' aria-valuenow='" + data['cpu']['percent_per'][k] + "' aria-valuemin='0' aria-valuemax='100' style='width: " + data['cpu']['percent_per'][k] + "%'>" + data['cpu']['percent_per'][k] + "%</div></div></td></tr>";
}
document.getElementById("tb_cpu_per").innerHTML = cpu_per; } //1.定义长连接
var conn = null; //2.定义连接函数 function connect() { disconnect();//把之前的连接关闭掉,在创建新的连接
//0.定义协议
var transports = ["websocket"];
//1.创建连接对象
conn = new SockJS("http://" + window.location.host + "/real/time",transports); //2.建立连接
conn.onopen = function () {
console.log('连接成功')
};
//3.建立接收消息
conn.onmessage = function (e) {
// console.log(e.data);
update_ui(e)
};
//4.建立关闭连接
conn.onclose = function (e) {
console.log("断开连接");
};
setInterval(function () {
//2.1 建立连接发送消息
conn.send("system")
},1000)
} function disconnect() {
if (conn != null) {
conn.close();
conn = null;
} } if (conn == null) {
connect(); } else {
disconnect();
}

monitor.js

 2.内存+交互分区信息实时展示(仪表图)

import tornado.web
from app01.views.views_commen import CommnHardler
from app01.tools.monitor_tools import monitor_obj
from app01.tools.chart import Chart
#定义1个首页的视图 class IndexHandler(CommnHardler):
def get(self,*args,**kwargs):
cpu_info = monitor_obj.cpu()
cpu_percent_avg_info=cpu_info ['percent_avg']#CPU平均使用率
cpu_percent_per=cpu_info ['percent_per'] #每个CPU使用率
mem_info=monitor_obj.mem()
swap_info=monitor_obj.swap()
print(swap_info)
c=Chart()
liquid_html=c.liquid_html(chart_id='cpu_avg',title="cpu平均使用率",val=cpu_percent_avg_info)
self.render("index.html",**{
"liquid_html":liquid_html,
"cpu_percent_per_info" :cpu_percent_per,
"mem_html":c.guage_html("mem","内存使用率",mem_info['percent']),
"swap_html":c.guage_html('swap',"交互分区使用率",swap_info['percent']),
"mem_info":mem_info,"swap_info": swap_info
})

view_index.py

//进度条变化
function progress_status(val) {
var data = "";
if (val >= 0 && val < 25) {
data = " bg-success";
} else if (val >= 25 && val < 50) {
data = "";
} else if (val >= 50 && val < 75) {
data = " bg-warning";
} else if (val >= 75 && val <= 100) {
data = " bg-success";
}
return data
} function update_ui(e) {
var data=JSON.parse(e.data);
//因为pyechart 声明了变量option_cpu_avg所有我们只需要修改现有的变量option_cpu_avg即可
option_cpu_avg.series[0].data[0]=(data['cpu']['percent_avg']/100).toFixed(4); //保留4位小数
option_cpu_avg.title[0].text=data['dt']+'CPU平均使用率';
//保证对option_cpu_avg的修改生效
myChart_cpu_avg.setOption(option_cpu_avg);
//-------------------------------------------------------
var cpu_per = "";
for (var k in data['cpu']['percent_per']) {
var num = parseInt(k);
cpu_per += "<tr><td class='text-primary' style='width: 30%'>CPU" + num + "</td>";
cpu_per += "<td><div class='progress'><div class='progress-bar progress-bar-striped progress-bar-animated" + progress_status(data['cpu']['percent_per'][k]) + "' role='progressbar' aria-valuenow='" + data['cpu']['percent_per'][k] + "' aria-valuemin='0' aria-valuemax='100' style='width: " + data['cpu']['percent_per'][k] + "%'>" + data['cpu']['percent_per'][k] + "%</div></div></td></tr>";
}
document.getElementById("tb_cpu_per").innerHTML = cpu_per; /*内存实时更新*/
option_mem.series[0].data[0].value = data['mem']['percent'];
option_mem.title[0].text = data["dt"] + "-内存使用率";
myChart_mem.setOption(option_mem);
document.getElementById("mem_percent").innerText = data['mem']['percent'];
document.getElementById("mem_total").innerText = data['mem']['total'];
document.getElementById("mem_used").innerText = data['mem']['used'];
document.getElementById("mem_free").innerText = data['mem']['free']; /*交换分区实时更新*/
option_swap.series[0].data[0].value = data['swap']['percent'];
option_swap.title[0].text = data["dt"] + "-交换分区使用率";
myChart_swap.setOption(option_swap);
document.getElementById("swap_percent").innerText = data['swap']['percent'];
document.getElementById("swap_total").innerText = data['swap']['total'];
document.getElementById("swap_used").innerText = data['swap']['used'];
document.getElementById("swap_free").innerText = data['swap']['free']; } //1.定义长连接
var conn = null; //2.定义连接函数 function connect() { disconnect();//把之前的连接关闭掉,在创建新的连接
//0.定义协议
var transports = ["websocket"];
//1.创建连接对象
conn = new SockJS("http://" + window.location.host + "/real/time",transports); //2.建立连接
conn.onopen = function () {
console.log('连接成功')
};
//3.建立接收消息
conn.onmessage = function (e) {
// console.log(e.data);
update_ui(e)
};
//4.建立关闭连接
conn.onclose = function (e) {
console.log("断开连接");
};
setInterval(function () {
//2.1 建立连接发送消息
conn.send("system")
},1000)
} function disconnect() {
if (conn != null) {
conn.close();
conn = null;
} } if (conn == null) {
connect(); } else {
disconnect();
}

monitor.js

{% extends "layout.html" %}<!--继承布局文件-->
{% block head %}
<script src="{{ static_url('echarts-liquidfill/echarts-liquidfill.min.js') }}"></script>
{% end %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="card text-white bg-dark mb-3">
<div class="card-header">CPU信息</div>
<div class="card-body">
<div class="row">
<div class="col-md-6 pad-left">
<table class="table table-responsive-sm table-bordered">
<thead>
<th colspan="2">所有CPU使用率</th>
</thead>
<tbody id="tb_cpu_per">
{% for k,v in enumerate(cpu_percent_per_info) %}
<tr>
<td class="text-primary" style="width: 30%">
CPU{{ k }}
</td>
<td>
<div class="progress"> <!---进度条!-->
<div class="progress-bar progress-bar-striped progress-bar-animated {{ handler.progress_status(v)}}"
role="progressbar" aria-valuenow="{{ v }}" aria-valuemin="0"
aria-valuemax="100" style="width:{{ v }}%">{{ v }}%
</div>
</div>
</td>
</tr>
{% end %}
</tbody>
</table>
</div> <div class="col-md-6 pad-right">
<div class="border border-white">{% raw liquid_html%}</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-12">
<div class="card text-white bg-dark mb-3">
<div class="card-header">内存/交互分区信息</div>
<div class="card-body">
<div class="row">
<div class="col-md-6 pad-left">
<div class="border border-white">{% raw mem_html %}</div>
<table class="table table-sm table-bordered">
<tr>
<td class="text-primary" style="width: 30%">使用率(%)</td>
<td id="mem_percent" class="text-danger">{{ mem_info['percent'] }}</td>
</tr>
<tr>
<td class="text-primary" style="width: 30%">总量(GB)</td>
<td id="mem_total" class="text-danger">{{ mem_info['total'] }}</td>
</tr> <tr>
<td class="text-primary" style="width: 30%">使用量(GB)</td>
<td id="mem_used" class="text-danger">{{ mem_info['used'] }}</td>
<tr>
<tr>
<td class="text-primary" style="width: 30%">剩余量(GB)</td>
<td id="mem_free" class="text-danger">{{ mem_info['free'] }}</td>
</tr>
</table>
</div>
<div class="col-md-6 pad-right">
{% raw swap_html %}
<table class="table table-sm table-bordered">
<tr>
<td class="text-primary" style="width: 30%">使用率(%)</td>
<td id="swap_percent" class="text-danger">{{swap_info['percent']}}</td>
</tr>
<tr>
<td class="text-primary" style="width: 30%">总量(GB)</td>
<td id="swap_total" class="text-danger" >{{swap_info['total']}}</td>
</tr> <tr>
<td class="text-primary" style="width: 30%">使用量(GB)</td>
<td id="swap_used" class="text-danger">{{swap_info[ 'used']}}</td>
<tr>
<tr>
<td class="text-primary" style="width: 30%">剩余量(GB)</td>
<td id="swap_free" class="text-danger">{{swap_info['free']}}</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{% end %}

index.html

--------------支持web socket的协议

import json
from sockjs.tornado import SockJSConnection#专门生成web_socket服务
from app01.tools.monitor_tools import Monitor
class RealTimeHandler(SockJSConnection):
waiters=set()#定义1个客户端连接池,所有客户端共用1个集合
#1.建立连接(不存在重复的连接)
def on_open(self, request):
try:
self.waiters.add(self)
except Exception as e:
print(e)
#2.发送消息
def on_message(self, message):#接收客户端消息,根据客户端发送过来的消息返回一些数据!
try:
if message =="system":
m=Monitor()
data={"mem":m.mem(),"swap":m.swap(),"cpu":m.cpu(),"disk":m.disk(),"net":m.net(),'dt':m.dt}
self.broadcast(self.waiters,json.dumps(data,ensure_ascii=False))#把消息广播给所有连接的客户端
except Exception as e:
print(e)
#3.关闭连接
def on_close(self):
try:
self.waiters.remove(self)
except Exception as e:
print(e)

view_real_time.py

web socket协议

参考

pyecharts

Tornado实现监控数据实时展示的更多相关文章

  1. 学会这一招,小白也能使用数据可视化BI软件创建医院数据实时展示大屏

    灯果数据可视化BI软件是新一代人工智能数据可视化大屏软件,内置丰富的大屏模板,可视化编辑操作,无需任何经验就可以创建属于你自己的大屏.大家可以在他们的官网下载软件.   本文以医院数据实时展示大屏为例 ...

  2. 手把手教你轻松使用数据可视化BI软件创建某疾病监控数据大屏

    灯果数据可视化BI软件是新一代人工智能数据可视化大屏软件,内置丰富的大屏模板,可视化编辑操作,无需任何经验就可以创建属于你自己的大屏.大家可以在他们的官网下载软件.   本文以某疾病监控数据大屏为例为 ...

  3. 性能测试 CentOS下结合InfluxDB及Grafana图表实时展示JMeter相关性能数据

    CentOS下结合InfluxDB及Grafana图表实时展示JMeter相关性能数据   by:授客 QQ:1033553122 实现功能 1 测试环境 1 环境搭建 2 1.安装influxdb ...

  4. openshift 容器云从入门到崩溃之十《容器监控-数据展示》

    POD资源历史曲线(CPU.内存.网络) 监控方案heapster+hawkular-metrics+hawkular-cassandra heapster负责收集数据 hawkular-cassan ...

  5. SpringBoot 2.0 + InfluxDB+ Sentinel 实时监控数据存储

    前言 阿里巴巴提供的控制台只是用于演示 Sentinel 的基本能力和工作流程,并没有依赖生产环境中所必需的组件,比如持久化的后端数据库.可靠的配置中心等.目前 Sentinel 采用内存态的方式存储 ...

  6. UAVStack JVM监控分析工具:图形化展示采集及分析监控数据

    摘要:UAVStack推出的JVM监控分析工具提供基于页面的展现方式,以图形化的方式展示采集到的监控数据:同时提供JVM基本参数获取.内存dump.线程分析.内存分配采样和热点方法分析等功能. 引言 ...

  7. Druid:一个用于大数据实时处理的开源分布式系统——大数据实时查询和分析的高容错、高性能开源分布式系统

    转自:http://www.36dsj.com/archives/28590 Druid 是一个用于大数据实时查询和分析的高容错.高性能开源分布式系统,旨在快速处理大规模的数据,并能够实现快速查询和分 ...

  8. SpringCloud学习笔记(5):Hystrix Dashboard可视化监控数据

    简介 上篇文章中讲了使用Hystrix实现容错,除此之外,Hystrix还提供了近乎实时的监控.本文将介绍如何进行服务监控以及使用Hystrix Dashboard来让监控数据图形化. 项目介绍 sc ...

  9. 《大数据实时计算引擎 Flink 实战与性能优化》新专栏

    基于 Flink 1.9 讲解的专栏,涉及入门.概念.原理.实战.性能调优.系统案例的讲解. 专栏介绍 扫码下面专栏二维码可以订阅该专栏 首发地址:http://www.54tianzhisheng. ...

随机推荐

  1. java - day010 - 基本类型包装,自动装箱和拆箱,日期,集合

    基本类型的包装类 byte Byte short Short int Integer long Long float Float double Double char Character boolea ...

  2. python实践总结与反思

    2019.6.20 python近期实践总结与反思 记录一些这两天python犯的一些低级却易犯的错误.千万不要犯第二次啊!! 1. py文件名字问题 py文件命名不能和调用的模块名一样! 比如,我要 ...

  3. Python使用selenium模拟点击,进入下一页(三)

    嗯,昨天呢,我们已经实现了自动输入百度然后搜索Cgrain,然后点击按钮,进入我的页面,在这里呢,有个问题 ActionChains(seleniumGoo).move_by_offset(-480, ...

  4. rsync 应用总结

    rysnc server端 1.vim /etcrsyncd.conf (用户rsync,目录,模块,非系统虚拟用户及密码文件) 2.创建共享目录 /data/www/{www,bbs,blog} 3 ...

  5. IPC五种通讯方式

    IPC五种通讯方式 1.管道:速度慢,容量有限,只有父子进程能通讯 2.FIFO:任何进程间都能通讯,但速度慢 3.消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题 ...

  6. idea maven配置

    转载自:https://www.cnblogs.com/Silencepeng/p/7444012.html 一.下载maven的包 http://www.apache.org/ 1.在网页中打开上面 ...

  7. 执行mysql脚本文件

    一般都是连接mysql执行sql语句: 在命令行下输入 mysql -h localhost -u root -p回车,然后输入密码即可; 或直接运行mysql自带的连接工具,然后输入密码即可. 执行 ...

  8. 类对象传输到jsp页面。需要转换为js的json对象时,这么做。

    场景:要从一个列表中选择信息,填写入父页面的表单中,但是字段非常多... 后台查询,得到结果,放在列表中. 效果:点击选择产品.. 弹出页面:点击后面的选择产品 选择产品后:信息自动填充.. 实现:点 ...

  9. PHP mysqli_debug() 函数

    定义和用法 mysqli_debug() 函数用于执行调试操作. 注释:为了使用该函数,您必须编译 MySQL 客户端库来支持调试.

  10. 005_FreeRTOS任务挂起和恢复

    (一) (二)使用,参数是任务句柄 //key任务函数 void key_task(void *pvParameters) { u8 key; ) { key=KEY_Scan(); switch(k ...