领克车展 Max Co币机诞生记
网上拿张现场图

先引用一篇时下2019上海车展文章的段落

上海车展新车满满,领克展台玩起了“骚操作”
https://chejiahao.autohome.com.cn/info/3658165

在7.1展台的另一个区域,我们却发现了一个与众不同的品牌,它就是领克。
今年的领克展台同样人山人海,别人车展上都在秀车,而领克却走出了"不寻常的路"。
这次领克展台主题围绕"全能进阶,不酷不耍",八个大字出发,
除了为消费者带来全新的产品外,更是将都市游乐场搬到了展台现场,通过场景化、沉浸式的体验让消费者体会到"不止于车"的独特品牌魅力。
进入展厅,仿佛置身一场潮流盛宴。超级蹦床、能量燃吧、赛道玩家、MAX扭蛋机……简直就是一场属于年轻人的潮流大趴。

"不寻常的路"、"全能进阶,不酷不耍"、"不止于车",打造一个车展游戏厅,而我在车展的贡献就是用双手孵化出了游戏厅的 Max Co币机。

来一个现场 Max Co币机 的位置

车是红花,我是绿叶,就是这样甘于在幕后默默奉献!

好,诞生记开始吧!

brief
活动流程概要:

现场用户手机端参与活动,获得币,生成二维码。
用户在出币机扫码识别,机器出币。
现场消费,余币存入。

兵马未动粮草先行,分析功能要求,分工协作,规划接口是非常重要的。
假设机器是已经有了,串口通信
那么创建三个进程
1号进程socket服务端和串口通信功能
2号进程socket客户端和外网通信功能
3号负责扫码输入与2号进程通信

本文主要围绕机器出币的环节进行分析

出币机实物实现的功能要求:
用户扫描生成好的二维码,出币机显示扫码内容;
将扫描到的二维码信息发送服务器查询验证;
通过验证则允许出币机出N个币;
出币结果发送回服务器保存;
没有通过验证则反馈给用户未通过原因。

//==========================================================
出币机项目外网接口规划

网络交互分为三个步骤:查询验证,请求出币锁定,反馈结果解锁

第一步,查询验证接口:

如果把二维码当作电子钞票存单
则二维码内的信息应当包括:
1兑换通讯网址,如http://xx.com

2存单币个数,如10

3存单号(Key),如(数据表id的md5)xxxx,通过key能查询到存单的信息

4需要的其他信息 other

字符串的形式如
http://xx.com?c=10&k=xxxx&o=other

这相当与是验证出币的通讯接口和发送的数据信息

接口反回json形式

正常时举例
{
"response":1,
"description":"接口正常",
"data":{
"shuliang":10,
"duihuan":0,
"lock":false
}
}

response是返回状态值,出币机程序根据值做出判断
description是描述
data是存单数据
数据里有 通过key查数据库得到的存单信息
shuliang 存单币的数量
duihuan 已经兑换的数量
lock 出币时的锁定状态
以上的示例说明 数量有10个,已经兑换0个
并且没有请求过出币锁定,出币机根据信息判断可以请求锁定,然后出币10个

下面的示例说明全部兑换完毕,出币机不能出币

{
"response":1,
"description":"接口正常",
"data":{
"shuliang":10,
"duihuan":10,
"lock":false
}
}

出错时
{
"response":0,
"description":"接口响应失败,服务器错误什么的"

}

第二步,请求出币锁定状态接口:
针对多台终端的考虑
需要禁止一码同时多机扫描的问题,增加一个接口限制
终端出币前先将状态锁住
出币完成反馈结果时再将状态解锁。

1通讯网址,如http://xx.com?action=lock

2兑换Key,如(数据表id的md5)xxxx

3出币数量 10

4出币机器id CB01

5服务器端验证的操作码mmmm (2 3 4 项的字符串连接起来,取其md5值 )如 MD5(xxxx10CB01)

http://xx.com?action=lock&&c=10&mid=CB01&code=mmmm

后台检查shuliang>duihuan && !lock时返回示例

{
"response":1,
"description":"锁住成功",
"data":{
"shuliang":10,
"duihuan":0,
"lock":true
}
}
否则返回
{
"response":0,
"description":"重复请求出币锁定",
"data":{
"shuliang":10,
"duihuan":0,
"lock":true
}
}

{
"response":0,
"description":"余额不足",
"data":{
"shuliang":10,
"duihuan":10,
"lock":false
}
}
出错时
{
"response":0,
"description":"接口响应失败,服务器错误什么的"

}

第三步,结果反馈接口:

1通讯网址,如http://xx.com?action=save

2兑换Key,如(数据表id的md5)xxxx

3出币数量 10

4出币机器id CB01

5服务器端验证的操作码mmmm (2 3 4 项的字符串连接起来,取其md5值 )如 MD5(xxxx10CB01)

!!!!!!注意 3出币数量    !!!!!!!!!!!!! 它的值是出币机实际出来的数量>=0。数据库里duihuan的值应该累加上本次提交的值
本接口要实现解锁的功能

get发送字符串的形式如
http://xx.com?action=save&k=xxxx&c=10&mid=CB01&code=mmmm

返回示例
{
"response":1,
"description":"接口正常",
"data":{
"shuliang":10,
"duihuan":10,
"lock":false
}
}

出现错误时
{
"response":0,
"description":"接口响应失败,服务器错误什么的"

}
出现错误出币机本地记录错误日志。

//==========================================================

出币机项目内网和串口接口

出币机内嵌入pc机

出币单元是下位机,与pc串口通讯

pc屏幕显示从扫码到出币的交互过程。

分两部分
服务端
显示端

服务端实现一个socket server,并负责串口与下位机的连接通讯

显示端连接socket,负责发送扫码信息,验证和显示每个交互步骤的信息反馈。

上位机下位机串口通讯
通讯字节码数据协议
四个字节一个数据包

FX XX XX CC 
FX开头  
第一字节是命令
 二三字节是数据
CC验证字节是数据一二三字节的和

查询状态命令

F0 00 00 F0
返回
F0 XX XX CC

F0 00 00 F0 空闲状态
F0 00 01 F1 忙

空闲状态接受出币请求
执行出币
F1 0X XX CC

返回
F1 0X XX CC或 F1 FX XX CC或 F1 EX XX CC 或忙状态

F1 F 是成功完成出币  
F1 E 是完成部分出币

XXX 是12位的币数量

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
在以上基础上增加包头FF和包尾FE
FF XX XX XX CC FE

其中XX XX XX CC四个字节是上面说的数据
整个通讯包包含了 包头 数据 校验 包尾

通讯实际数据举例

查询状态命令
FF F0 00 00 F0 FE
返回
FF F0 XX XX CC FE

XX XX 为单片机内部运行的状态数值, 00 00 空闲状态。

空闲状态接受出币请求
执行出币(执行出5个币)
FF F1 00 05 F6 FE

返回

正常启动时
FF F1 00 05 F6 FE
正常结束
FF F1 F0 05 E6 FE
错误(只出3个币后中止)
FF F1 E0 03 D4 FE
忙时返回当前状态
FF F0 XX XX CC FE

socket通讯为json字串

{
action:出币
data:10

}

{
action:出币完成
data:10

}

{
action:部分出币
data:5

}

{
action:请求状态
data:0

}

{
action:返回状态
data:0

}

通讯json字符串以base64编码后同样加 头FF 尾FE 分隔

形式如 FF base64stringdata FE

尽管后期接口文档都会有改动,不过将来的事将来再说,到现在,有了规划,项目可以启动了。

事先的假设并不存在,所以我任务的第一步得创造一个能听从串口指挥出币的机器。

万能的X宝找,并没有这种机器,不过有能数币的机器,拿来改造吧。用现成的驱动电路,arduino做个控制板。

顺丰到付一个,一觉醒来,嗖~~到了,真快!

测试数币后马上拆机,

传感器就一个光眼,执行器就一个马达,


主控芯片信号给mos管控制马达正反转,马达带动孔盘转动,掉进孔内的币,被机械上的挤出结构挤出,挤出同时光眼传感器被触发,
计数并显示在数码管上,机器的只能用按键设定数币数量。

意料之中,只能抛弃原来主控,只利用主板的电机驱动部分。

开始电路分析,


电路图反编译,

幸好不是多层板,放大镜翻来覆去的看,出来了。

控制信号


打孔飞线改板,把需要的接口焊接好插针

arduino编程,主要逻辑:

接收到出币指令,正转。
中断触发计数,
满足出币数量,刹停,反馈出币数量。

正转持续3秒,若不出币,停,反转一下,停。反复
连续第4次不出币,刹停,反馈出币数量。

要快捷的开发还是使用rtos框架

概括的分几个任务,具体看源码
比如串口通信协议解析任务。
根据状态控制正反转的任务。
中断触发容易抖动的延时计数任务。

面包板上搭个结构。

视频

编程源码

#include <Arduino_FreeRTOS.h>
#include <semphr.h>// add the FreeRTOS functions for Semaphores (or Flags).
#include "DataPkg.h"// Declare a mutex Semaphore Handle which we will use to manage the Serial Port.
//声明信互斥信号量用它来管理串口
// It will be used to ensure only only one Task is accessing this resource at any time.
//某时刻只有一个任务能获取到串口通讯这个资源
SemaphoreHandle_t xSerialSemaphore;
//
int _status = 0; //运行状态
int _f = 1; //方向
int _setCount = 0; //出币数
int _empty = 0; //5秒内没有出币  的次数
int _tick_fz = 0; //反转的tick
int _tick_zh = 0; //正反转换时延时,status=10
int _speed = 0;
int _gonglv = 4; //x分之一
int work_finish = 0; //工作完成中止,工作未完成中止
bool _blink_enable = true;
int _blink_count = 0;
int _bring_up=0;//重启时_bring_up初始为0,启动成功为1
int cornCount = 0;//统计出币数量
bool chk_request=false;
int value_sigma=0;
int value_times=0;
void init_var(){_status = 0; //运行状态_f = 1; //方向_setCount = 0; //出币数_empty = 0; //5秒内没有出币  的次数_tick_fz = 0; //反转的tick_tick_zh = 5; //正反转换时延时,status=10_speed = 0;_gonglv = 4; //x分之一work_finish = 0; //工作完成中止,工作未完成中止_blink_enable = true;_blink_count = 0;//中断消抖延时计数cornCount = 0;
}
void power_on_run(){//开机bringup反转响应_tick_fz=60;_f = -1;  _status=12;}
unsigned int _gonglv_count = 0;
//串口数据解析
unsigned char sbuf[16];
unsigned char pkg_data_buf[16];
unsigned char send_data_buf[6];//发送数据的容器
int pkg_data_len = 0; //不为0说明有命令
DataPkg pkg = DataPkg();
void on_pkg_data_ok(unsigned char arr[], int len) {for (int i = 0; i < len; i++) {pkg_data_buf[i] = arr[i];}pkg_data_len = len;/*if(_status==0){//解析数据,返回状态或出币}else{//返回忙状态。}*/
}// define two Tasks for TaskReadSerial & TaskCheckSpeed &TaskDriveMotor
//定义3个任务TaskReadSerial TaskCheckSpeed和 TaskDriveMotorvoid TaskReadSerial( void *pvParameters );//解析命令开始动作
void TaskCheckSpeed( void *pvParameters );//检查出币速度,5秒无出币,反转
void TaskDriveMotor( void *pvParameters );//根据状态切换驱动信号
void TaskInerrupt0Effect( void *pvParameters );
void TaskBlink1Delay( void *pvParameters );//中断消抖延时int lastcornCount = 0;volatile int state = LOW;void blink1()
{chk_request=true;value_sigma=0;value_times=0;
}
void corn_count_times(){_setCount -= 1;_speed += 1;_empty = 0;if (_setCount == 0) {_status += 100;_tick_fz = 0;}//oldstate = !state;cornCount += 1;
}
void blink2(){if (_blink_enable) {_blink_enable = false; //禁止中断,中断消除抖动_blink_count = 0; //延时计数corn_count_times();}
}
//设置中断
void setupInerrupt0()
{//attachInterrupt(0, blink, CHANGE);//attachInterrupt(pin, ISR, mode)attachInterrupt(digitalPinToInterrupt(2), blink1, FALLING); //(recommended)//attachInterrupt(digitalPinToInterrupt(pin), ISR, mode); (recommended)//attachInterrupt(interrupt, ISR, mode); (not recommended)//attachInterrupt(pin, ISR, mode); (Not recommended. Arduino SAMD Boards, Uno WiFi Rev2, Due, 101 only)//mode:定义中断触发类型,有四种形式://LOW:低电平触发;//CHANGE:电平变化触发;//RISING :上升沿触发(由LOW变为HIGH);//FALLING:下降沿触发(由HIGH变为LOW);//Due板子还支持高电平触发。
}void setMotorPins() {//4,5正反方向 L298N控制方式, 4高5低正 4低5高反 45电平相同停止,同时0为安全电平pinMode(4, OUTPUT);digitalWrite(4, 0);pinMode(5, OUTPUT);digitalWrite(5, 0);//6789 H桥控制方式,拉低通电 对应电路板1234线 14 低电平正 23低电平反转12和34不能同时拉低否则短路!!! 同时为1为安全电平pinMode(6, OUTPUT);digitalWrite(6, 1);pinMode(7, OUTPUT);digitalWrite(7, 1);pinMode(8, OUTPUT);digitalWrite(8, 1);pinMode(9, OUTPUT);digitalWrite(9, 1);
}
void setMotorT() {//4,5正反方向 L298N控制方式, 4高5低正 4低5高反 45电平相同停止,同时0为安全电平digitalWrite(4, 0);digitalWrite(5, 0);//6789 H桥控制方式,拉低通电 对应电路板1234线,14 低电平正转23低电平反转  12和34不能同时拉低否则短路!!! 同时为1为安全电平digitalWrite(6, 1);digitalWrite(7, 1);digitalWrite(8, 1);digitalWrite(9, 1);}
void setMotorSTOP() {//制动// L298N控制方式停默认制动digitalWrite(4, 0);digitalWrite(5, 0);//6789 H桥控制方式 79负极相通制动digitalWrite(6, 1);digitalWrite(7, 0);digitalWrite(8, 1);digitalWrite(9, 0);}
void setMotorZ() {setMotorT();_gonglv_count++;if (_gonglv_count % _gonglv == 0) {digitalWrite(4, 1);digitalWrite(6, 0);digitalWrite(9, 0);}}
void setMotorF() {setMotorT();_gonglv_count++;if (_gonglv_count % _gonglv == 0) {digitalWrite(5, 1);digitalWrite(7, 0);digitalWrite(8, 0);}}
// the setup function runs once when you press reset or power the board
// 当上电或reset时setup函数只运行一次
void setup() {setMotorPins();delay(1000);setupInerrupt0();// initialize serial communication at 9600 bits per second://初始化串口连接波特率Serial.begin(115200);while (!Serial) {; // wait for serial port to connect. Needed for native USB, on LEONARDO, MICRO, YUN, and other 32u4 based boards.//等待串口连接。需要LEONARDO, MICRO, YUN, 或其他 32u4板子上的usb接口}pkg.dispatch_event = &on_pkg_data_ok;int l = pkg.getDatLen();Serial.println(l);Serial.println(pkg.pkgLen);Serial.println("pkg ready!");// Semaphores are useful to stop a Task proceeding, where it should be paused to wait,//信号量在需要让一个任务进程停一会儿时很有用,它让任务处于暂停等待的状态,// because it is sharing a resource, such as the Serial port.//就像串口这样的共享资源// Semaphores should only be used whilst the scheduler is running, but we can set it up here.//信号量应该只在调度运行的时候使用,在这里我们可以对它进行设置。if ( xSerialSemaphore == NULL )  // Check to confirm that the Serial Semaphore has not already been created.//检查确认串口信号没被创建。{xSerialSemaphore = xSemaphoreCreateMutex();  // Create a mutex semaphore we will use to manage the Serial Port//创建信号来管理串口if ( ( xSerialSemaphore ) != NULL )xSemaphoreGive( ( xSerialSemaphore ) );  // Make the Serial Port available for use, by "Giving" the Semaphore.//调用xSemaphoreGive (“给”信号)使串口处于有效状态(没被占用)}Serial.println("SemaphoreGive");// Now set up Tasks to run independently.//现在设置多个个任务互不影响的运行。xTaskCreate(TaskInerrupt0Effect,  (const portCHAR *) "Inerrupt0Effect",  128  // Stack size,  NULL,  2  // Priority,  NULL );xTaskCreate(TaskBlink1Delay,  (const portCHAR *) "TaskBlink1Delay",  128  // Stack size,  NULL,  3  // Priority,  NULL );xTaskCreate(TaskDriveMotor,  (const portCHAR *) "TaskDriveMotor",  128  // Stack size,  NULL,  2  // Priority,  NULL );xTaskCreate(TaskCheckSpeed,  (const portCHAR *) "TaskCheckSpeed",  128  // Stack size,  NULL,  1  // Priority,  NULL );// Now the Task scheduler, which takes over control of scheduling individual Tasks, is automatically started.//现在,任务调度程序开始自动控制调度单个任务,它将自动启动。//*/Serial.println("tasks is running now.");/*_bring_up=1;_setCount = 10;_status = 10;_tick_zh = 5;*/power_on_run();
}void loop()
{// Empty. Things are done in Tasks.//所有事情都在每个任务里做,这里留空。
}/*--------------------------------------------------*/
/*---------------------- Tasks ---------------------*/
/*--------------------------------------------------*/
void TaskDriveMotor(void *pvParameters __attribute__((unused))) {for (;;) // A Task shall never return or exit.{if (_status > 0 && _status <= 10) {setMotorT();//设置停00if(_bring_up==0){_bring_up=1;_status += 20;continue;}if (_empty >= 3) {_status += 50;continue;}if (_tick_zh > 0) {//转换延时vTaskDelay((100L * configTICK_RATE_HZ) / 1000L);_tick_zh -= 1;continue;}if (_f == 1) {_status += 1; //11}if (_f == -1) {_status += 2; //12}}if (_status == 11) {//设置正  1  0setMotorZ();if (_f == -1) {_status -= 1; //10_tick_zh = 5;}}if (_status == 12) {//设置正  0  1setMotorF();_tick_fz -= 1;if (_tick_fz <= 0) {_f = 1;}if (_f == 1) {_status -= 2; //10_tick_zh = 5;}}if (_status > 90) {//设置  0  0setMotorT();setMotorSTOP();_status = 0; //0work_finish = 1;}if (_status > 40 && _status < 90) {//设置  0  0setMotorT();setMotorSTOP();//发送无币_status = 0; //0work_finish = 2;}if (_status > 20 && _status < 40) {//设置  0  0setMotorT();setMotorSTOP();//bringup后发送_status = 0; //0work_finish = 3;}if (_status == 0) {//设置  0  0setMotorT();setMotorSTOP();}vTaskDelay((30L * configTICK_RATE_HZ) / 1000L);}
}void TaskCheckSpeed(void *pvParameters __attribute__((unused))) {for (;;) // A Task shall never return or exit.{if (_status == 11 && _f == 1) { //正转,并且没有请求反转的时候 检查出币_speed = 0;vTaskDelay((3000L * configTICK_RATE_HZ) / 1000L);//正转等3秒检测出币if (_speed == 0 && _status == 11) {_empty += 1;_tick_fz = 30; //反转时间_f = -1; //请求反转} else {_speed = 0;}} else {vTaskDelay((200L * configTICK_RATE_HZ) / 1000L);}}
}void TaskInerrupt0Effect( void *pvParameters __attribute__((unused)) )  // This is a Task.
{int pin = 13;pinMode(pin, OUTPUT);for (;;){digitalWrite(pin, state);if (lastcornCount != cornCount) {lastcornCount = cornCount;/*if ( xSemaphoreTake( xSerialSemaphore, ( TickType_t ) 5 ) == pdTRUE ){Serial.print("ZZZZ--------:");Serial.println(cornCount);xSemaphoreGive( xSerialSemaphore );}*/}vTaskDelay(10);}
}void TaskBlink1Delay( void *pvParameters __attribute__((unused)) )  // This is a Task.
{for (;;){if(chk_request){  if(digitalRead(2)==HIGH){value_sigma++;}value_times++;if(float(value_sigma)/float(value_times)>0.5&&value_times>=4){chk_request=false;//计数corn_count_times();}}if (!_blink_enable) {_blink_count++;if (_blink_count >= 8) {_blink_enable = true;}} else {}while (Serial.available()) {//byte s=Serial.read();int numdata = Serial.readBytes(sbuf, 1);//Serial.write(sbuf,numdata);pkg.writeArrayToBuf(sbuf, numdata);//Serial.write('\n');//for(byte i=0;i<numdata;i++){}//判断命令if (pkg_data_len > 0) {pkg_data_len = 0;bool need_reply = false;send_data_buf[0] = 0xff;send_data_buf[1] = 0x00;send_data_buf[2] = 0x00;send_data_buf[3] = 0x00;send_data_buf[4] = 0x00;send_data_buf[5] = 0xfe;//三个字节一个命令//F0 00 00查询状态if (pkg_data_buf[0] == 0xF0) {send_data_buf[1] = 0xF0;send_data_buf[2] = 0x00;send_data_buf[3] = _status;send_data_buf[4] = (send_data_buf[1] + send_data_buf[2] + send_data_buf[3]) & 0xff;need_reply = true;}//F1 0X XX 请求出币----------------------------------------------!!!!!!!!!!!!!!if (pkg_data_buf[0] == 0xF1) {if (_status == 0) {send_data_buf[1] = 0xF1;send_data_buf[2] = pkg_data_buf[1];send_data_buf[3] = pkg_data_buf[2];send_data_buf[4] = (send_data_buf[1] + send_data_buf[2] + send_data_buf[3]) & 0xff;init_var();_setCount = ((pkg_data_buf[1] & 0x0F) << 8 )+ pkg_data_buf[2];_status = 10;  Serial.write(_setCount);        need_reply = true;} else {send_data_buf[1] = 0xF0;send_data_buf[2] = 0x00;send_data_buf[3] = _status;send_data_buf[4] = (send_data_buf[1] + send_data_buf[2] + send_data_buf[3]) & 0xff;need_reply = true;}}if (need_reply) {need_reply = false;//串口发送数据if ( xSemaphoreTake( xSerialSemaphore, ( TickType_t ) 5 ) == pdTRUE ){Serial.write(send_data_buf, 6);xSemaphoreGive( xSerialSemaphore );}}}if (work_finish > 0) {send_data_buf[0] = 0xff;send_data_buf[1] = 0x00;send_data_buf[2] = 0x00;send_data_buf[3] = 0x00;send_data_buf[4] = 0x00;send_data_buf[5] = 0xfe;if (work_finish == 1) {work_finish = 0;send_data_buf[1] = 0xF1;send_data_buf[2] = 0xF0|(0x0F & (cornCount >> 8)); //成功完成出币send_data_buf[3] = 0xFF & (cornCount);send_data_buf[4] = (send_data_buf[1] + send_data_buf[2] + send_data_buf[3]) & 0xff;} else if(work_finish == 2){work_finish = 0;send_data_buf[1] = 0xF1;send_data_buf[2] = 0xE0|(0x0F & (cornCount >> 8)); //部分完成出币(币数量不够)send_data_buf[3] = 0xFF & (cornCount);send_data_buf[4] = (send_data_buf[1] + send_data_buf[2] + send_data_buf[3]) & 0xff;}else if(work_finish == 3){//bringup 完成主动发送一次空闲状态work_finish = 0;send_data_buf[1] = 0xF0;send_data_buf[2] = 0x00; send_data_buf[3] = 0x00;send_data_buf[4] = (send_data_buf[1] + send_data_buf[2] + send_data_buf[3]) & 0xff;}work_finish = 0;//串口发送数据if ( xSemaphoreTake( xSerialSemaphore, ( TickType_t ) 5 ) == pdTRUE ){Serial.write(send_data_buf, 6);xSemaphoreGive( xSerialSemaphore );}}vTaskDelay(1);//15*n  ms}
}

验证程序。
视频

真实驱动装机测试,控制板内部控制出5个币

视频

好啦,完成硬件编程再来用万能的python搞搞软件,废话不说直接上马。

python开发1号进程部分源码


import socketserver
import socket
import struct
from socketserver import StreamRequestHandler as SRH
from time import ctime
import timefrom cbj_serial_data_pkg import CBJ_SerialDataPkg
from cbj_serial_data_pkg_parser import CBJ_SerialDataPkg_Parser
from serails_mng import SerialOP
from event import *from jsonconfig import loadConfig
from base64_json_pkg import Base64JsonPkgData
from fffe_pkg_parser import FFFEPkgParser
import json
from log_saver import *
logging_init("TCP_SERVER","logs")tcp_server_host = '127.0.0.1'
tcp_server_port = 9999
serials_config=["COM21", 112500]#
#外部参数加载
setting=loadConfig("config.json")
tcp_server_host=setting['tcp_server_host']
tcp_server_port=setting['tcp_server_port']
serials_config=setting['serials_config']#串口数据包解析
serial_pkg = CBJ_SerialDataPkg_Parser()
#base64 json 数据转换
b64json_data=Base64JsonPkgData()
#socket 包解析
skt_pgk=FFFEPkgParser()#dat_arr=b64json_data.encodeToPkg("{\"abc\":\"我们\",\"o\":{\"abc\":456}}")
#启动串口设备并检查通讯#
queue = []
def closeallsock():print("closeallsock", queue)for sockhandle in queue:sock=sockhandle.requestprint(sock)sockhandle.izclosed = 1sock.shutdown(2)sock.close()for sockhandle in queue:try:queue.remove(sockhandle)except ValueError:pass
def sendallsock(data):for sockhandle in queue:try:sockhandle.wfile.write(data)except ValueError:passwait_bringup=0
# 接收到串口发来的数据包
def on_serial_data_ok(e):# 判断serial数据# 创建分发json数据# print(senddata)# 分发给客户端d=e.datajsondata={}if(d[0]==0xF0):#状态jsondata["action"]="status"jsondata["data"] = d[2]global wait_bringupwait_bringup=1passif (d[0] == 0xF1):# 完成反馈状态jsondata["data"] = ((d[1]&0x0F)<<8)+d[2]if(d[1] ==0):jsondata["action"] = "workstart"elif (d[1]&0xF0 == 0xF0):#全部jsondata["action"] = "workfinish"elif(d[1]&0xF0 == 0xE0):#部分jsondata["action"] = "worktimeout"log_info("from serial:%s"%str(jsondata))# 转换为base64串arr_b64 = b64json_data.encodeObjToPkg(jsondata)# 转换为socket包FFxxxxFEskt_data = skt_pgk.makeToPkg(arr_b64)#print(arr_b64)sendallsock(skt_data)pass#接收到客户端发来的数据包
def on_skt_data_ok(e):#print("on_skt_data_ok",e.data)#解析json数据err, jsondata = b64json_data.decodeFromPkg(e.data)#print(err)#print(jsondata)if(err==0):log_info("from sktclient:%s" % str(jsondata))#print("action" in jsondata)#print(jsondata["action"]=="getwork")if "action" in jsondata :if(jsondata["action"] == "getwork" ):if "data" in jsondata:if(jsondata["data"] > 0):print("出币动作",jsondata["data"])sop.d.setDataToOutCorn(jsondata["data"])sop.serialSend()passelif(jsondata["action"] == "getstatus"):sop.d.setDataToCheckStatus()sop.serialSend()pass#创建下发数据#dat_arr = b64json_data.encodeToPkg(json.dumps(jsondata))# sop.testCreateDataToSend()# sop.serialSendData([0xff,0xf0,0,0,0xf0,0xfe])#下发pass
#监听
serial_pkg.add_event_listener(PKGEvent.PKG_DATA_OK, on_serial_data_ok)
skt_pgk.add_event_listener(PKGEvent.PKG_DATA_OK, on_skt_data_ok)
sop = SerialOP(serials_config[0], serials_config[1],serial_pkg)
#TCP服务端
class Servers(SRH):def handle(self):self.izclosed=0log_info('got connection from %s'% str(self.client_address))#self.request.setblocking(0)#设置为非组赛模式strdata = 'connection %s:%s at %s succeed!' % (tcp_server_host, tcp_server_port, ctime())self.wfile.write(strdata.encode())queue.append(self)while True:try:data = self.request.recv(8192)  # .decode()#print(data)except socket.error:self.request.shutdown(2)self.request.close()print("eeee")breakdataLen = len(data)#print(dataLen,data)if (dataLen > 0):'''if(dataLen>pkg.pkgLen):ndata=data[dataLen-pkg.pkgLen:dataLen]print("!!! Too Many Data, Ignore length:%d" % (dataLen-pkg.pkgLen))dataLen = pkg.pkgLenelse:ndata = data'''ndata = dataarraydata = struct.unpack(str(dataLen) + "B", ndata)skt_pgk.writeArrayToBuf(arraydata)#print("Recv Total length:%d" % dataLen, len(arraydata))else:self.request.shutdown(2)self.request.close()print("empty")breaklog_info(' %s closed' % str(self.client_address))queue.remove(self)if __name__ == '__main__':log_info("正在等待出币机启动")while (wait_bringup == 0):print(wait_bringup)time.sleep(1)log_info("出币机启动成功")tst=0if(tst):print("测试开始")#测试#创建jsonjsondata = {}jsondata["action"] = "getstatus"jsondata["data"] = 22#转换为base64串arr_b64=b64json_data.encodeObjToPkg(jsondata)#转换为socket包FFxxxxFEskt_data=skt_pgk.makeToPkg(arr_b64)print(skt_data)# 模拟接收#skt_pgk.writeArrayToBuf([1, 254, 255, 3, 43, 254, 3, 41, 254, 255, 3, 43, 254, 3, 41, 254, 255, 3, 43, 254, 255, 8])#skt_pgk.writeArrayToBuf([255,101, 121, 74, 104, 89, 109, 77, 105, 79, 105, 76, 109, 105, 74, 72, 107, 117, 54, 119, 105, 76, 67, 74, 118, 73, 106, 112, 55, 73, 109, 70, 105, 89, 121, 73, 54, 78, 68, 85, 50, 102, 88, 48, 61,254])skt_pgk.writeArrayToBuf(skt_data)log_info("server is running....")server = socketserver.ThreadingTCPServer((tcp_server_host, tcp_server_port), Servers)server.daemon_threads=Trueserver.serve_forever()closeallsock()

2号进程部分源码

#coding=utf-8
import socket,select,threading,sys
import struct
from event import *
from base64_json_pkg import Base64JsonPkgData
from fffe_pkg_parser import FFFEPkgParser
#from web_api import *
import web_api
from jsonconfig import loadConfig
import input_qrcode_str
import input_qrcode_recver
from log_saver import *
import sound_player
import string
logging_init("TCP_CLIENT","logs")
# 外部参数加载
setting = loadConfig("config.json")
#print (setting["url_ck"],setting["url_lk"],setting["url_sv"])
web_api.url_ck=setting["url_ck"]
web_api.url_lk=setting["url_lk"]
web_api.url_sv=setting["url_sv"]
web_api.url_ad=setting["url_ad"]
#存币使能
enable_add=setting["enable_add"]
#print(web_api.url_ck, web_api.url_lk)
client_addr = (setting["tcp_server_host"],setting["tcp_server_port"])  # equals server_addr()
#print(client_addr)
CBJ_id=setting["CBJ_id"]
log_info(CBJ_id)no_error=True
scan_is_ok=False
action_status=0
corn_cont_send=0
corn_count=0
qr_code="67acc2862ffb3fb3f0219eec2569df0fa88bdd4714f161f57e81f1d41e90311645a9d498b916d250"
qr_code="67acc2862ffb3fb32d0ba295b7ec2916a88bdd4714f161f54965d8aa8986d75845a9d498b916d250"
remark= "出币反馈"
#监听二维码输入事件
def on_qr_ok(e):global enable_addglobal qr_codeglobal scan_is_okglobal action_statuslog_info("接收二维码"+str(e.data))if(not scan_is_ok):log_info("系统开始响应")qr_code=e.data#"67acc2862ffb3fb32d0ba295b7ec2916a88bdd4714f161f54965d8aa8986d75845a9d498b916d250"#这里添加了新的规则  add$  开头的码为存币请求#检查是否是存币码,如果是action_status=1000进入存币流程ck=qr_code.split("$", 1)if (len(ck)==2 and ck[0]=="add" and enable_add):action_status=1000scan_is_ok=Trueelse:log_info("系统忙,请稍后扫码")
input_qrcode_recver.input_qr_EVT.add_event_listener(PKGEvent.PKG_DATA_OK, on_qr_ok)#检查CBJ状态反馈事件
checkBusyEVT=EventDispatcher()
def on_not_busy_ok(e):global action_statuslog_info("出币空闲状态"+str(e.data))if(scan_is_ok and action_status==1):action_status=2
checkBusyEVT.add_event_listener(PKGEvent.PKG_DATA_OK, on_not_busy_ok)# base64 json 数据转换
b64json_data = Base64JsonPkgData()
# socket 包解析
skt_pgk = FFFEPkgParser()#接收到服务端发来的数据包
def on_skt_data_ok(e):global action_statusglobal corn_countglobal checkBusyEVT#print("on_skt_data_ok",e.data)#解析json数据err, jsondata = b64json_data.decodeFromPkg(e.data)#print(err)#print(jsondata)if(err==0):#print("action" in jsondata)#print(jsondata["action"]=="getwork")if "action" in jsondata :if(jsondata["action"] == "workstart" ):if "data" in jsondata:if(jsondata["data"] > 0):log_info("正在执行出币动作"+str(jsondata["data"]))action_status=10passelif(jsondata["action"] == "workfinish"):if "data" in jsondata:log_info("成功完成执行出币动作"+str(jsondata["data"]))action_status=100corn_count=jsondata["data"]passelif (jsondata["action"] == "worktimeout"):if "data" in jsondata:log_info("超时,部分出币动作完成"+str(jsondata["data"]))action_status=101corn_count = jsondata["data"]passelif (jsondata["action"] == "status"):if "data" in jsondata:log_info("出币机状态"+str(jsondata["data"]))if(jsondata["data"]==0):checkBusyEVT.dispatch_event(PKGEvent(PKGEvent.PKG_DATA_OK, 0))pass
#监听
skt_pgk.add_event_listener(PKGEvent.PKG_DATA_OK, on_skt_data_ok)
# 倾听其他成员谈话
def listening(cs):global no_errorinputs = [cs]while True:rlist,wlist,elist = select.select(inputs, [], [],0.5)# client socket就是用来收发数据的, 由于只有这个waitable 对象, 所以不必迭代if cs in rlist:try:# 打印从服务器收到的数据data=cs.recv(1024)except socket.error:no_error = Falselog_error("socket is error")exit()dataLen = len(data)if (dataLen > 0):ndata = dataarraydata = struct.unpack(str(dataLen) + "B", ndata)skt_pgk.writeArrayToBuf(arraydata)#print("Recv Total length:%d" % dataLen, len(arraydata))# 发言
def speak(cs):global no_errorwhile True:try:data = input()except Exception as e:log_error( "can't input")no_error = Falseexit()# if data == "exit":#     cs.close()#     breaktry:cs.send(data.encode())except Exception as e:no_error = Falseexit()def main():import time# client socketcs = socket.socket()notconn=1while(notconn):try:cs.connect(client_addr)log_info("连接服务器成功")notconn=0except:log_info("无法连接服务器,,,10秒后重连")time.sleep(10)# 分别启动听和说线程t = threading.Thread(target=listening,args=(cs,))  # 注意当元组中只有一个元素的时候需要这样写, 否则会被认为是其原来的类型t.daemon=Truet.start()t1 = threading.Thread(target=speak,args=(cs,))t1.daemon=Truet1.start()# 创建jsonjsondata = {}jsondata["action"] = "getstatus"jsondata["data"] = 22# 转换为base64串arr_b64 = b64json_data.encodeObjToPkg(jsondata)# 转换为socket包FFxxxxFEskt_data = skt_pgk.makeToPkg(arr_b64)slp=0.5global scan_is_okglobal action_statusglobal corn_cont_send#接口参数需要global qr_codeglobal corn_countglobal remarkglobal CBJ_idwhile no_error:if(not scan_is_ok):#67acc2862ffb3fb32d0ba295b7ec2916a88bdd4714f161f54965d8aa8986d75845a9d498b916d250# print("等待扫描")time.sleep(slp)else:if(action_status==0):# 根据二维码准备好数据action_status=1# 创建json发送验出币机是否空闲的请求jsondata = {}jsondata["action"] = "getstatus"jsondata["data"] = 0# 转换为base64串arr_b64 = b64json_data.encodeObjToPkg(jsondata)# 转换为socket包FFxxxxFEskt_data = skt_pgk.makeToPkg(arr_b64)cs.send(skt_data)elif(action_status==1):time.sleep(slp)#等待出币机回答是否空闲的状态elif(action_status==2):action_status = 3time.sleep(slp)#进入网络验证log_info("调用网络验证")d = {'data': qr_code}web_api.do_start_actions(web_api.url_ck, web_api.url_lk,None,d)elif(action_status==3):#等待网络验证time.sleep(slp)elif (action_status == 4):action_status = 5# 等待网络验证jsondata = {}jsondata["action"] = "getwork"jsondata["data"] = corn_cont_send# 转换为base64串arr_b64 = b64json_data.encodeObjToPkg(jsondata)# 转换为socket包FFxxxxFEskt_data = skt_pgk.makeToPkg(arr_b64)cs.send(skt_data)elif (action_status == 5):# 等待出币time.sleep(slp)elif (action_status == 10):# 等待出币time.sleep(slp)elif (action_status == 100 or action_status == 101 ):action_status = 200log_info("调用发送出币结果")d = {'data': qr_code, 'mid': CBJ_id, 'status': 1, 'coTotal': corn_count, 'remark': remark}web_api.do_stop_actions(web_api.url_sv, None, d)time.sleep(slp)elif (action_status == 200):# 等待保存time.sleep(slp)elif (action_status >= 500 and action_status < 1000):# 取币流程结束保存结束action_status =0scan_is_ok=0time.sleep(slp)elif (action_status ==1000):action_status = 1001# 调用存币接口log_info("正在请求存入一个币")tst_post_data_ad = {'data': qr_code, 'coTotal': 1}web_api.do_add_actions(web_api.url_ad, None, tst_post_data_ad)elif (action_status ==1001):#等待存币返回time.sleep(slp)elif (action_status >=1500  and action_status < 2000):#存币流程结束成功action_status = 0scan_is_ok = 0time.sleep(slp)#cs.send(skt_data)#time.sleep(slp)#time.sleep(slp)cs.shutdown(socket.SHUT_RDWR)cs.close()def on_start_ok(e):global action_statusglobal corn_cont_sendlog_info("main通知下位机出币"+str(e.data))corn_cont_send=e.data[2]action_status = 4web_api.startActionEVT.add_event_listener(PKGEvent.PKG_DATA_OK, on_start_ok)
def on_stop_ok(e):global action_statusglobal corn_cont_sendglobal scan_is_oklog_info("main通知重新开始"+str(e.data))#这里可以根据返回的data做出响应code_num=e.data[0]if (code_num >= 10000 and code_num < 20000):sound_player.playSoundByCodeNumber(code_num)  # 声音反馈action_status = 500passelif(code_num >= 20000 and code_num < 30000):sound_player.playSoundByCodeNumber(code_num)  # 声音反馈action_status = 500passelif (code_num >= 30000 and code_num < 40000):sound_player.playSoundByCodeNumber(code_num)  # 声音反馈action_status = 500passelif (code_num >= 40000 ):if(code_num == 40000 ):if(int(e.data[2])>0):sound_player.playSoundByCodeNumber(40001)  # 声音反馈log_info("存入%s个币" % e.data[2])else:sound_player.playSoundByCodeNumber(40002)  # 声音反馈log_info("0效果的存币操作")else:sound_player.playSoundByCodeNumber(40003)  # 声音反馈log_info("存币操作错误")passaction_status = 1500else:action_status = 500#scan_is_ok = Falseweb_api.stopActionEVT.add_event_listener(PKGEvent.PKG_DATA_OK, on_stop_ok)if __name__ == "__main__":#global scan_is_okscan_is_ok=Falselog_info("创建接收二维码扫描线程")#t = threading.Thread(target=input_qrcode_str.listening_input)t = threading.Thread(target=input_qrcode_recver.recv_input_str)t.daemon = Truet.start()log_info("创建客户端线程")main()

3号进程源码

import mmap
import contextlib
import time
import file_lock
def init_file():try:f = open("test1.dat", "w")file_lock.lock(f, file_lock.LOCK_EX)f.write('\x00' * 1024)file_lock.unlock(f)f.close()except Exception as ex:print("Error Info: %s" % (str(ex)))finally:print("init ok")f.close()
init_file()
print("-----")
time.sleep(1)
#with open("test1.dat", "w") as f:#f.write('\x00' * 1024)
while 1:#with open('test1.dat', 'r+') as f:try:data = input("please input:\n")f = open("test1.dat", "r+")file_lock.lock(f, file_lock.LOCK_EX)#print(f.fileno())with contextlib.closing(mmap.mmap(f.fileno(), 1024, access=mmap.ACCESS_WRITE)) as m:#print(data)m.seek(0)s = "" + data#buf = 1024*b'0'#相当于bytes(1024) ,不可修改buf=bytearray(1024)#可修改a=s.encode("utf-8")buf[:len(a)]=a#print(buf)m.write(buf)m.flush()except Exception as ex:print("Error Info: %s" % (str(ex)))finally:file_lock.unlock(f)f.close()print("input ok")time.sleep(2)

软硬件联调测试没问题,开启批量生产模式。

批量焊接控制板改主板。

由于第一块拆机的主板长时间运行发现mos管有漏电,单片机给了信号也拉不低,出现电机不转的情况,不能因小失大,毁了活动现场就杯具了,干脆每块板换了mos管。


增加了光眼上拉电阻,消抖电容,运行时我心里更踏实一些。


最后原来主板按键的接口改为跟控制板连接的接口,这样免去了在主板打孔焊针的工作。

做了两天的手工,十几个主控主板终于完工


机器的外壳很重要,要高端大气上档次,选用稳重的黑色喷涂。满足人机工程学,内部还要有合理的布局。
购置好了电料,直奔工厂车间安装。

工厂内逐一进行系统调试。

一次一个视频

开始组装,接线拧螺丝。干到手抽筋。

新烤好的蛋糕出炉啦!

质检环节,调试出币视频

打包物流发出啦!

宝贝诞生了!你的职责就是助力领克车展活动,你我都是在默默无闻的幕后工作,共同加油!希望大家能喜欢,谢谢!

2019领克车展 Max Co币机诞生记相关推荐

  1. 吉利博瑞星越缤瑞领克01/02/03车机安装第三方软件carplay启用教程,附最新可用dns

    最新可用dns:103.40.20.159 吉利领克车机安卓版的都可以用这个dns来安装任意软件,也可以安装领克AUFN carplay ,完美支持!需要的车友看下边的教程 领克车机最新DNS查询方法 ...

  2. roads 构筑极致用户体验_坚持用户思维 推动领克汽车逆势突围

    [2020年7月9日,杭州]近日,全球新高端品牌-领克公布了2020年6月销量数据.领克汽车6月实现月销量达13214辆,环比增长约2%,同比增长约53%,连续三个月获得双增长,并创下过去七个月以来最 ...

  3. 领克汽车是用鸿蒙系统吗,领克全新旗舰SUV将在上海车展首发亮相

    作为和吉利与沃尔沃汽车联合打造的品牌,领克已经在高端品牌领域逐渐站稳脚跟,甚至销量已经反超部分合资品牌. 当前,领克已经推出了领克01.02.05等SUV,主要集中在紧凑型或小型级别车型,这种打发虽然 ...

  4. 领克linux系统怎么下载软件,新升级的领克车机系统好用吗?我们来盘一下

    提到车机系统,可以说是人们日常用车中常常被忽视的,但又是每天都在接触的配置.一套好的车机系统,不仅仅可以为人们提供丰富的娱乐体验,而且可以为驾驶提供便捷.主打潮流.科技.运动的领克汽车一直以来吸引了无 ...

  5. 领克02linux车机怎么升级,你们想看的领克02长测报告来了,一篇读懂02的车机系统...

    [汽车之家 长期测试] 来到了第三期,咱就聊聊年轻人所关注的一项配置吧:车机系统.其实不仅是领克,近两年推出的很多新车型都喜欢把车机系统作为卖点,这无可厚非,毕竟一个看上去更科幻.功能集成度更高的车机 ...

  6. 领克安卓车机DNS安装第三方软件

    领克安卓车机DNS安装第三方软件 更新 域名获取方式 接口域名 关键接口 已经帮你们整理好了,直接[Postman](https://app.getpostman.com/run-collection ...

  7. 轮距和轴距有什么区别_大热的“机能风”是什么?看完领克02 PHEV你就懂了

    [爱卡汽车 新能源频道原创] 在当今的时尚领域,"机能风"是一个重要的分支,它融合了潮流设计和功能性面料,是时尚与科技的结合.放眼汽车行业,如果要选出一款车作为"机能风& ...

  8. dns 领克_领克03 1.5T 半年9600公里使用感受分享

    先上一张领克APP 首保的完成说明(不要问为什么配图不是03,颜色也不对,后续还会对这个吐槽 车子详细型号是2019 款 03 1.5T 劲pro 白色,去年10月底提车,落地不算利息 15.6,供各 ...

  9. 车市下滑 领克汽车为什么逆势上扬?

    云栖号案例库:[点击查看更多上云案例] 不知道怎么上云?看云栖号案例库,了解不同行业不同发展阶段的上云方案,助力你上云决策! 2018年末开始,寒潮席卷中国汽车市场.热了20多年的中国车市,迎来首次销 ...

最新文章

  1. Linux那些事儿之我是Sysfs(5)举例二sculld
  2. mysql+存储过程+删除重复数据_mysql 存储过程 删除重复
  3. 清华数为大数据应用低代码开发工具DWF 2021成长回顾
  4. epoll监听文件_【原创】万字长文浅析:Epoll与Java Nio的那些事儿
  5. discuzx2.5添加自定义积分日志
  6. 极客Web前端开发资源集锦
  7. 微信小程序实战–集阅读与电影于一体的小程序项目(八)
  8. vue 方法获取返回值_vue.js - vuex异步提交,怎么获取返回数据
  9. tomcat bug之部署应用的时候经常会发上startup failed due to previous errors
  10. C++ int转string以及源码
  11. 整型数据类型java_Java 六种基本整型数据类型变量的取值范围
  12. 面试了8家公司,他们问了我这些机器学习题目……
  13. 为什么国内的网盘公司都在 TB 的级别上竞争,成本会不会太高?
  14. android DisplayMetrics
  15. fabric.js自定义字体的引入
  16. 一次解锁三个BIG分析图制作思路,畅快!
  17. 电脑中的驱动程序是什么,是干什么的
  18. html5 手机站点,HTML5移动端手机网站基本模板 HTML5基本结构
  19. 法学专业能从事计算机工作吗,未来20年,这5个专业都是“香饽饽”,毕业生工作好找前途大好!...
  20. ubuntu16.04编译ORBSLAM2问题解决

热门文章

  1. 【NOIP2014】生活大爆炸版石头剪刀布
  2. 洛谷P1151 子数整数(问题转化,透过现象看本质)
  3. 旅行青蛙南の旅旅行券_旅行时查找WiFi
  4. PID调谐方法:根据开环响应特性调谐(一)
  5. Mixamo不仅是可商用的免费模型动画库,还是一个在线绑定蒙皮神器
  6. Across the great wall we can reach every corner in the world
  7. 非三星手机无法登录三星账号_如何解决所有三星手机的烦恼
  8. 基于ResNetRS的宝可梦图像识别
  9. 计算机连接网络被限制,电脑连接wifi出现网络受限的解决方法
  10. 数字图像处理——隐形眼镜缺陷检测算法