介绍一下SIM900A 和以前做的一个小例子

在安信可的A6、A7模块出来之前,SIM800、SIM900系列是市场上应用最为广泛的GSM & GPRS模块。

虽然WIFI、ZIGBEE等组网越来越广泛,但是GSM&GRPS依旧有无法替代的优势,那就是有GSM&GPRS信号的地方就可以使用。想象你在深山老林里采集气象数据或者监视某些自动化设备的运行状态,扯个光纤宽带然后开个WIFI热点? 那么你需要问问电业部门愿不愿意给你扯电,还有网络服务商愿不愿意给你布线。当然了,你愿意出个十几二十万的布线费,估计也有可能请动两尊大神。但是如果你采用2G方案呢,GSM/GPRS信号已经覆盖了一般的山林,你弄套设备,在配个蓄电池就可以开工啦。

SIM900A 的控制

SIM900A的控制非常简单,使用AT指令。具体AT指令可以参考SIM900A的手册,或者参考ETSI(欧洲电信标准协会)的相关文档,比如:

  • GSM 03.04
    Digital cellular telecommunications system;
    Signalling requirements relating to routeing of calls to mobile subscribers
  • GSM 07.05
    Digital cellular telecommunications system (Phase 2+);
    Use of Data Terminal Equipment - Data Circuit terminating;
    Equipment (DTE - DCE) interface for Short Message Service (SMS) and Cell Broadcast Service (CBS)
  • GSM 07.07
    Digital cellular telecommunications system (Phase 2+);
    AT command set for GSM Mobile Equipment (ME)

等等

以打电话为例,我在Arduino上用如下简单代码即可实现:

1
2
3
4
5
6
7
8
9
10
11
12
void setup() {               
Serial.begin(115200);
}

void loop() {
delay(8000);
Serial.print("AT\r");
delay(2000);
Serial.print("ATD135142XXXXXX;\r");
delay(10000);
Serial.print("ATH\r");
}

一个以前的应用实例

额,其实我买sim900A主要是想玩些物联网方向的应用的。但是却跑偏了


请原谅为啥我的板子看起来比较难看,主要是这个模块对电源电流啥的要求太苛刻了,于是串了个IN4007的二极管,并了1000uf的电容,一个470uf的电容,这样才能连上网。

我想干啥呢,就是之前用一个手机卡注册过一些业务,部分操作需要收取校验码,但是找遍手头破手机不是没有电池就是没有充电器,好不容易发现一个能点亮的手机,还是移动定制机,无法使用联通卡。总不能为了这点破事买个新手机,或者去营业厅剪成小卡。愁煞我也!然后发现手头有个sim900A的模块,可以通过AT指令操作。那么就用它来看看能不能胜任这个任务吧。

  • 看,我能用自己写的程序读出短信并解析了

  • 写了个简单对话框程序,这样看起来像模像样了

  • 对话框程序还能发短信,这是我收到的哦

当时做这个程序遇到的一个难点就是中文短信编解码的问题,那个PDU格式可是极其复杂的。

总结

SIM900A 是个很好玩的模块哦
能收能发短信,能打电话
之前我讲过用微信开关灯的例子,如果换成用sim900A则更简单,发个短信:关灯灯就关了。

另外,发短信功能是挺好玩,但是不要用来做群发器啊,否则你懂的。

还有,这是几年前做的小例子,设备早跑角落里吃灰了,懒得重新为文章配图,直接用以前的吧 😀


This page is synchronized from the post: 介绍一下SIM900A 和以前做的一个小例子

聊聊点赞金额 ,发钱啦(限中文: 前3名回复送价值2SBD的点赞,前4-10送1SBD点赞)

今天的帖子聊了文章奖励的问题
也来说一下帖子的奖励

得出的结论,帖子金额和以下因素有关:

  • net_rshares: 帖子得到的投票产生的rshares
  • total_rshares 系统总投票产生的rshares
  • 奖池总金额
  • SBD/STEEM 七日均价

我们知道帖子的net_rshares是由一堆点赞者点出来的,那么既然如此,我们是否可以计算出我们的点赞金额呢?好像有好几个网站可以计算我们单次点赞的金额,但是毕竟跑网站上去算比较麻烦,如果能在程序中算出来那就最好了。

计算点赞金额

之前帖子中我们得到的,帖子奖励的SBD 近似表示公式:
奖池金额 x (net_rshares / total_rshares ) x STEEM 7日均价
那么点赞的SBD其实也应该类似
奖池金额 x (点赞rshares / total_rshares ) x STEEM 7日均价

所以计算点赞rshares 是计算点赞收益的关键
而点赞rshares是由

  • 用户持有的等效SP (SP + 别人代理的 - 代理给别人)
  • 用户当前的Voting Power
  • 用户点赞的 100%

另外,硬叉19之后点赞威力变成了四倍,所以还应该 x 4
按上述分析我计算出来的rshares比实际点赞的产生的rshare多了一倍 (额)
不管了,除以二,一切正常。

  • 关于STEEM POWER
    硬叉18后引入了SP代理功能
    所以STEEMPOWER 要将用户持有的SP, 比如代理过来的SP,代理给别人的SP都核算进来,才是用户的实际有效SP

  • 关于Voting Power
    系统中读回的Voting Power是最近一次点赞后的Voting Power值
    而Voting Power是随时间线性恢复的,所以为了精准,我们需要计算距离上次点赞操作的时间差值,并根据差值以及Voting Power的恢复速度计算实时的voting Power值。

由金额反过来计算百分比

既然我们可以按上述描述计算点赞金额,那么我们是否可以通过指定金额来计算应该设置的点赞比重呢?通过变换公式,我们可以设定SBD金额后换算出应该设置的点赞百分比。

经过测试,一切正常。


然后,福利来了
试试我的计算程序有没有毛病,顺便也给大家发一些福利。
使用中文回复本贴,前3名送价值2SBD的点赞, 前4-10送1SBD点赞
(回复主贴哦,回复回复贴不计算在内,重复回复只计第一次)

对了,之前说过,系统以及价格因素,帖子的收益随时变动,所以点赞金额也一样。
上述金额,以我操作时为准,之后降价了可不负责补差价哦


感谢阅读 / Thank you for reading.
欢迎upvote、resteem以及 following me @oflyhigh 😎


This page is synchronized from the post: 聊聊点赞金额 ,发钱啦(限中文: 前3名回复送价值2SBD的点赞,前4-10送1SBD点赞)

也来说一下帖子的奖励

早晨起来看到@htliao在研究帖子收入减少的问题,很有意思的想法。
但是帖子的奖励是否和作者的活跃度有关呢,比如说频繁给别人点赞?
至少从代码上没看到直接的关联(当然代码我只知道一点皮毛)

其实帖子的奖励到底是怎么核算出来的,是我一直比较好奇的问题。但是之前一堆公式一堆曲线,实在是让人望而生畏,我觉得以我的智商,恐怕是研究不明白了。所以一直没敢碰这个雷区。但是不是说HF19之后改成线性回报曲线了吗?是不是应该简单一点啦。于是怀着忐忑的心情,来看看能不能找到点规律。

rshares 即奖励

我认为计算帖子收益的核心就是这两句代码,呃,为何是两句呢?
u256 claim = to256( evaluate_reward_curve( ctx.rshares.value, ctx.reward_curve, ctx.content_constant ) );
claim = ( claim * ctx.reward_weight ) / STEEMIT_100_PERCENT;
u256 payout_u256 = ( rf * claim ) / total_claims;

然后,evaluate_reward_curve中

1
2
3
case linear:
result = rshares;
break;

由此可见线性回报多好啊,至少对我们这些菜鸟而言,读代码不烧脑。

通过上述分析,我们得知,一个帖子的rshares即帖子的奖励,然后根据系统当前的一些情况,计算出来并已SBD的形式表现。

决定帖子rshares 的因素

那么rshares 哪里来的呢,其实就是别人(或自己)投票得来的
比如我的一个帖子部分投票列表

把整个列表中所有的rshares加起来就是帖子的net_rshares

所以对于帖子来说,影响奖励的内在因素是你得了多少票,尤其是大权重的票

其它因素(整个体系)

整个体系一些因素的变化也会影响帖子的奖励。
我大致总结如下,一个是奖池金额,奖池金额越大,帖子的奖励越多

另外一个就是所有帖子的总rshares (确切地说是总claim)
总rshares共分奖池金额,那么你的帖子rshares所占比例越小,你分到的金额越少。
u256 payout_u256 = ( rf * claim ) / total_claims;
公式就是这个

为何是total_claims而不是total_rshares, 这是因为有些帖子设置收益限额或者拒绝收益等等,这部分帖子不占用奖池金额。

通过上述分析和代码,我们可以看到,系统内部都是已STEEM进行核算的,所以算出来帖子的价格是多少个STEEM。而我们看到的帖子金额都是显示为XXXX SBD,所以还需要一步转换就是将STEEM 表示为SBD。

系统中用到的价格是:

这个价格据说是7日均价,具体核算过程我没研究,总之如果市场上steem价格一路走低,那么这个价格就会变低。

帖子奖励 SBD 表示

摒弃一些乱七八糟的因素,帖子奖励的SBD表示可以近似的表示为:

奖池金额 (net_rshares / total_rshares ) STEEM 7日均价

所以影响帖子奖励的因素:

  • net_rshares: 帖子得到的投票产生的rshares
  • total_rshares 系统总投票产生的rshares
  • 奖池总金额
  • SBD/STEEM 七日均价

奖池总金额咋产生和计算的呢,先不研究了。

验证

以这篇文章为例:
SteemData Notify 代码学习二: Confirmation Worker / Code Study of SteemData Notify: Part two

验证了一下上述分析

计算出的结果与帖子显示的金额完全相符
(别问我你看到的为啥不符,帖子金额在不断变化)

结论

帖子金额和以下因素有关:

  • net_rshares: 帖子得到的投票产生的rshares
  • total_rshares 系统总投票产生的rshares
  • 奖池总金额
  • SBD/STEEM 七日均价

去除系统等其它我们不可控制的因素,让更多的人给你投票,才是让其金额增长或者保持不下降的根本方法,和作者的活跃度无关的。

当然,作者越活跃,越可能结识更多的朋友,礼尚往来,互相支持,互相投票,到也不失为一个好办法。


This page is synchronized from the post: 也来说一下帖子的奖励

YY一下激光炮打蚊子

夏天最令人困扰的事是什么?对我而言,那就是蚊子!

或许是我的血液特别香甜,蚊子对我总是特别关照,只要我在的地方,蚊子几乎不咬其它人。当然每次大餐后的蚊子都因为伙食太好、吃得太饱导致行动迟缓,然后被我无情的歼灭。说到无情的歼灭,想起了一个笑话:

一只蚊子叮在左胳膊上大喝了一通,你被叮醒了,在你抡起右手要打蚊子的一刹那,蚊子对你说:“我身体里可流着你的血!”

家里有了小孩子之后又怕蚊香和杀虫剂有毒害,所以基本上都是靠纱窗阻挡,然后每天总是有那么一两个漏网之鱼,或许是开门的时候潜伏进来的吧。不知道你是否体验过与蚊子战斗一整晚的感觉,总之,不是很愉快。尤其是没吸到血之前,它还是飞得很快的,往书桌等角落里一藏,根本找不到。然后等你快熟睡的时候,它又嗡嗡嗡的飞了过来,不胜其扰啊。那些灭蚊灯之类的也试过,风扇的噪音嗷嗷大,灯光也很刺眼,然后几晚下来只抓到几个小蠓虫,蚊子们依旧逍遥法外。

于是乎,情不自禁的YY一下,能不能弄个激光炮打蚊子呢?

打蚊子激光炮功能模块

如果要实现激光炮打蚊子需要哪些模块呢?我分析了一下,大致需要以下内容:
1) 蚊子识别装置
2) 蚊子瞄准装置
3) 激光发射装置

关于识别装置,我想到的方法是弄个高清摄像头,最好带红外夜视功能的,呃,我真的不是要干坏事。然后用OpenCV来识别,话说OpenCV 既然能识别人脸、识别车牌,识别个蚊子应该也是可以的吧。当然了,只是我猜测是这样,具体能不能行还真不知道呢。当然了,摄像头得能灵活转动的,否则蚊子躲到摄像头后边岂不是就没辙了。

蚊子瞄准装置,当摄像头识别出来文字以后,我们就可以根据摄像头的角度以及蚊子在图像中的位置定位蚊子的坐标了。当然,光定位没有用,我们必须让我们的激光大炮瞄准蚊子。我想到的方法之一,是用一个360°旋转舵机+一个普通舵机,通过旋转舵机的旋转以及普通舵机控制角度,我们就可以瞄准蚊子啦。

激光发射装置,听着是不是很高大上,其实我们电脑淘汰下来的废旧DVD、CD刻录机里的激光管的功率足够消灭蚊子啦。赶紧开拆吧。话说前段时间我打算DIY一个激光雕刻机玩具,从朋友那划拉来二十几个光驱,拆了几个后DIY个歪歪扭扭的雕刻机后就对此丧失了兴趣,嗯,拆了改成打蚊子装置,似乎应该不错。

问题


(网上售卖的激光笔,据说功率可以打鸟)

把打蚊子装置分解成上边的模块后,看起来似乎没啥麻烦啊。感觉哪里不对呢,哦,是啦,我们还有运行OpenCV的控制板。好像似乎八成大概我写过运行OpenCV的相关帖子呢,搜索一下,果然有✌:

摄像头和OpenCV都有了,不过貌似识别蚊子还是个大难题啊。

至于控制舵机啥的更是小CASE,Wiring Pi 可以让你像使用Arduino一样来使用树莓派,香蕉派也有对应的Wiring Pi移植。好吧,定位蚊子也不是问题。

至于发射激光,不过就是通电断电,好像DVD里的激光器有三个线,+-电源线,加一个信号线,高电平开启、低电平关闭,具体是不是这样,我忘记了,呃。

总感觉忘记点啥呢?是啥呢?
对了,万一一个蚊子飞到我眼前,然后被我的大炮发现,然后…… 不敢想象下去了。
所以还需要有个判断别误伤的装置,不然把自己搞瞎或者毁容就乐子大了。
想到这里,觉得有点害怕,是否还有我没想到的弊端呢?这毕竟是大规模杀伤性武器啊,算了,不YY了。

结论

激光打蚊子,技术上应该可行,然而我的技术能不能达到实现这个的水准姑且不说,我还不想毁容呢。万一以后没钱了,我还打算靠脸吃饭呢,呃,好像泄露了什么了不得的秘密,不说了….. 蚊子喜欢喝我的血就喝吧,尽情畅饮吧!


感谢阅读 / Thank you for reading.
欢迎upvote、resteem以及 following me @oflyhigh 😎


This page is synchronized from the post: YY一下激光炮打蚊子

SteemData Notify 代码学习二: Confirmation Worker / Code Study of SteemData Notify: Part two

在上一篇文章中,我们学习了SteemData Notify后端代码中的Blockchain Worker

归纳起来就是

  • Blockchain Worker 将blockchain上和账户有关的动态抓取进来
  • 判断是否是SteemData Notify 注册(并确认)用户相关的操作以及是否是用户关心的操作
  • 如果是,写入数据库通知表notifications

今天我们来继续学习 Confirmation Worker, 看看基于尘埃支付认证到底是什么鬼?

源码在这里:
https://github.com/SteemData/notify.steemdata.com/blob/master/src/worker.py

Confirmation Worker

1
2
3
4
5
def run_confirmation_worker():
log.info('Starting the confirmation worker.')
b = Blockchain()
for transfer in b.stream(filter_by='transfer'):
confirm_user_settings(transfer)

关于Blockchain以及stream(self, filter_by: Union[str, list] = list(), *args, **kwargs)
昨天的帖子中已经介绍了
这段代码其实就是过滤区块链中的转账操作,并调用confirm_user_settings(transfer)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def confirm_user_settings(op):
if op['to'] != steem_wallet:
return
if len(op['memo'].strip()) == 24:
try:
_id = ObjectId(op['memo'].strip())
except Exception:
return
elif len(op['memo'].strip()) == 40:
_id = op['memo'].strip()
else:
return
settings = db.settings.find_one({'_id': _id})
if settings:
db.settings.update_one(
{'_id': _id, 'username': op['from']},
{'$set': {'confirmed': True}}
)
message = 'You have made the following changes:\n' + \
'Email: %s\n' % (str(settings['email'] or '-')) + \
'Telegram: %s\n' % (str(settings['telegram_channel_id'] or '-')) + \
'Notify account_update: %s\n' % str(settings['account_update']) + \
'Notify change_recovery_account: %s\n' % str(settings['change_recovery_account']) + \
'Notify request_account_recovery: %s\n' % str(settings['request_account_recovery']) + \
'Notify transfer: %s\n' % str(settings['transfer']) + \
'Notify transfer_from_savings: %s\n' % str(settings['transfer_from_savings']) + \
'Notify set_withdraw_vesting_route: %s\n' % str(settings['set_withdraw_vesting_route']) + \
'Notify withdraw_vesting: %s\n' % str(settings['withdraw_vesting']) + \
'Notify fill_order: %s\n' % str(settings['fill_order']) + \
'Notify fill_convert_request: %s\n' % str(settings['fill_convert_request']) + \
'Notify fill_transfer_from_savings: %s\n' % str(settings['fill_transfer_from_savings']) + \
'Notify fill_vesting_withdraw: %s\n' % str(settings['fill_vesting_withdraw'])
log.info('Confirmed the settings for user %s.' % op['from'])
if settings['email']:
send_mail(settings['email'], 'Update confirmed', message)
if settings['telegram_channel_id']:
send_telegram(settings['telegram_channel_id'], message)

其中:

1
2
if op['to'] != steem_wallet:
return

如果不是转网指定钱包,则略过,本例中,steem_wallet 定义为 @null
顺便说一下,如果要改为收费服务,把这个钱包改成接收费用的账户,并且设置一下检查金额即可
不过大牛们都看不上这些钱啦,哈哈

1
2
3
4
5
6
7
8
9
10
if len(op['memo'].strip()) == 24:
try:
_id = ObjectId(op['memo'].strip())
except Exception:
return
elif len(op['memo'].strip()) == 40:
_id = op['memo'].strip()
else:
return
settings = db.settings.find_one({'_id': _id})

你可能好奇为啥又有24又有40呢?到底多长呢?
我也好奇,后来分析了一下,应该是历史版本遗留问题,这段一会我们再详细讲,只需知道按转账的memo去数据库中查找设置即可。Memo即_id。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if settings:
db.settings.update_one(
{'_id': _id, 'username': op['from']},
{'$set': {'confirmed': True}}
)
message = 'You have made the following changes:\n' + \
'Email: %s\n' % (str(settings['email'] or '-')) + \
'Telegram: %s\n' % (str(settings['telegram_channel_id'] or '-')) + \
'Notify account_update: %s\n' % str(settings['account_update']) + \
'Notify change_recovery_account: %s\n' % str(settings['change_recovery_account']) + \
'Notify request_account_recovery: %s\n' % str(settings['request_account_recovery']) + \
'Notify transfer: %s\n' % str(settings['transfer']) + \
'Notify transfer_from_savings: %s\n' % str(settings['transfer_from_savings']) + \
'Notify set_withdraw_vesting_route: %s\n' % str(settings['set_withdraw_vesting_route']) + \
'Notify withdraw_vesting: %s\n' % str(settings['withdraw_vesting']) + \
'Notify fill_order: %s\n' % str(settings['fill_order']) + \
'Notify fill_convert_request: %s\n' % str(settings['fill_convert_request']) + \
'Notify fill_transfer_from_savings: %s\n' % str(settings['fill_transfer_from_savings']) + \
'Notify fill_vesting_withdraw: %s\n' % str(settings['fill_vesting_withdraw'])
log.info('Confirmed the settings for user %s.' % op['from'])
if settings['email']:
send_mail(settings['email'], 'Update confirmed', message)
if settings['telegram_channel_id']:
send_telegram(settings['telegram_channel_id'], message)

如果没有相关设置,当然不必说了,如果有则更新数据库中对应id以及对应用户的设置状态为确认(confirmed)。
后边的代码就很好理解啦,给用户发个通知,告诉他/她的最新设置情况。

BUG, BUG

好像昨天我们已经发现了一个BUG,今天,看了这段代码以后,发现了另外一处BUG。

1
2
3
4
5
6
settings = db.settings.find_one({'_id': _id})
if settings:
db.settings.update_one(
{'_id': _id, 'username': op['from']},
{'$set': {'confirmed': True}}
)

注意这段代码,我们知道用户的设置会生成一个唯一的_id
如果用户自己去确认(转账给null, 并附_id做memo), 这并没有什么问题。

但是我们想一种坏坏的情况: 你的设置,我去确认
呃,好吧,我可能不知晓你生成的_id
那么换一种玩法,我帮你设置,我帮你确认,会是什么情况?

1
2
3
4
db.settings.update_one(
{'_id': _id, 'username': op['from']},
{'$set': {'confirmed': True}}
)

好在,系统在更新数据库的时候检查了实际操作的用户 'username': op['from']
所以,我对你的账户进行设置并不会写入到库中,但是:
settings = db.settings.find_one({'_id': _id})
查询的时候,仅仅查询了ID
这样后边代码会继续执行,会给设置的telegram_channel_idemail发信息哦。
所以检查的时候,也应该加上'username': op['from']才更合理一些。

关于Memo长度


语言解释起来太过于苍白,直接上代码吧。
也就是说SHA1生成了就是40位的16进制字符串
至于24,咱就不去研究了,人家都改成新的了,咱在去研究老的,也没啥意思,是不?

关于ObjectId()

我们可以看到

1
2
3
4
5
6
7
8
9
if len(op['memo'].strip()) == 24:
try:
_id = ObjectId(op['memo'].strip())
except Exception:
return
elif len(op['memo'].strip()) == 40:
_id = op['memo'].strip()
else:
return

之前代码中,把memo读取来的数据转换成了 ObjectId 类型
from bson.objectid import ObjectId
这是因为之前的生成的memo值并非通过setting hash而来,而是setting插入数据库后生成的记录的_id
这个类型是ObjectId,所以字符串值必须转换成ObjectId才可以读取。

比如 @a-0 这个用户,在数据库中存储的_id


我用字符串的形式直接查找,是找不到内容的。


而用ObjectId就可以直接查到

至于后来为何长度为40的时候不用转换了,因为存储的方式变了呗。

总结

  • Confirmation Worker 将blockchain上给 @null 转账的数据抓过来
  • 通过memo 判断是否是SteemData Notify的用户设置确认信息
  • 如果是,将数据库中用户设置的状态修改为True

并且我们发现了一处小BUG。
其实我还发现一个问题啊,有点坏坏的,不过我不能说,说了我就成坏人了,至少我还没坏透呢。

这就是所谓的基于尘埃支付的确认啦,我也学会咯。

咦,一总结好像也没啥内容呢,那我为啥写了这么多呢? 咦,为啥这句话这么面熟呢?
初学者水平有限,如有谬误敬请指正,深表谢意啦。


感谢阅读 / Thank you for reading.
欢迎upvote、resteem以及 following me @oflyhigh 😎


This page is synchronized from the post: SteemData Notify 代码学习二: Confirmation Worker / Code Study of SteemData Notify: Part two

SteemData Notify 代码学习一: Blockchain Worker / Code Study of SteemData Notify: Part one

话说学习编码最好的方式就是读优秀的代码和写代码。
尤其是读优秀的代码,既然自己写的代码很垃圾,多读读人家大牛们的优秀作品,受一下熏陶,沾惹点仙灵之气也好。俗话说:读书破万卷,下笔如有神!,俗话还说:熟读唐诗三百首,不会作诗也会吟!,那我把大牛们的代码读几遍,是不是也会写出牛光闪闪的代码呢?

又扯远了,言归正传,昨天给大家介绍了一下SteemData Notify,觉得是个挺有意思的产品:

今天呢,就来学习一下它的代码,看看具体是如何实现相关功能的。

代码功能模块

代码托管在github上,地址是: https://github.com/SteemData/notify.steemdata.com

通过简单分析,可以知晓代码分为应用(网站)端和后端

  • 应用(网站)端 src/app.py
  • 后端 src/worker.py

应用端: 使用Python + Flask + MongoDB
后端则主要使用: Python + Steem官方Python库 + MongoDB

我们这节着重分析后端的代码

后端代码

源码在这里
https://github.com/SteemData/notify.steemdata.com/blob/master/src/worker.py

通过阅读main函数
我们可以知道代码分成三大逻辑块

  • blockchain worker
  • confirmation worker
  • notifier worker

顾名思义,分别区块链工作进程、确认进程、通知进程, 分别通过不同的命令行参数启动。
至于为何不使用线程?以我实际经验,官方的Python 库对线程不是特别友好,尤其是一些复杂情况,可能导致很多意想不到的问题,所以多跑俩进程貌似也没啥不好的。或许作者有别的方面的思量,就不得而知了。

这篇文章我们主要学习三者之一的Blockchain Worker

Blockchain Worker

函数名: run_blockchain_worker()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    b = Blockchain()
types = [
'account_update',
'change_recovery_account',
'request_account_recovery',
'transfer',
'transfer_from_savings',
'set_withdraw_vesting_route',
'withdraw_vesting',
'fill_order',
'fill_convert_request',
'fill_transfer_from_savings',
'fill_vesting_withdraw',
]

b = Blockchain() 定义了Blockchain 实例
其本质呢,就是封装了一些对steem 节点的API操作
其中一个基本的操作是:
stream(self, filter_by: Union[str, list] = list(), *args, **kwargs)
简单的解释就是把steem区块链上发生的操作yield成操作流,然后可以针对不同的操作类型进行处理,比如说投票机器人就是Yield出来文章流,然后投票。

types = [ xxxx]
这一大堆,就是上述函数中的filter_by参数,用来过滤抽取我们需要的相关操作,忽略掉无关内容

有了上述对Blockchain以及filter_by的讲解,下面的代码就很好理解了

1
2
3
4
5
6
7
8
9
10
11
12
try:
block = db.last_processed_block.find_one()
start_block = int(block['block_num']) - 1
except Exception:
start_block = None
for op in b.stream(filter_by=types, start_block=start_block):
processed = db.processed_blockchains.find({'_id': op['_id']}).count()
if not processed:
if parse_blockchain(op):
db.processed_blockchains.insert_one(op)
db.last_processed_block.delete_many({})
db.last_processed_block.insert_one(op)

大致就是,把我们关心的操作读取回来,并且写到数据库中。

processed = db.processed_blockchains.find({'_id': op['_id']}).count()
判断我们是否已经处理过了,如果已经处理过,略过。

parse_blockchain(op)
这个判断是否是我们关心的数据,稍后详细讲

db.processed_blockchains.insert_one(op)
将op 插入我们处理过的op列表(mongodb)

db.last_processed_block.delete_many({})
db.last_processed_block.insert_one(op)
其实就是保存一下我们处理到哪里了,这样一旦因故障等问题中断,我们还可以接续上。

parse_blockchain(op)

前文我们说过,这个来获取我们关心的操作,这个是如何实现的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def parse_blockchain(op):
settings = None
message = None

if op['type'] == 'account_update':
settings = find_user_settings(op['account'])
if settings and settings['account_update']:
message = 'Received event: account_update (%s)' % op['account']

elif op['type'] in ['transfer', 'transfer_from_savings']:
settings = find_user_settings(op['from'])
if settings and settings[op['type']]:
message = 'Received event: %s\nEvent detail: %s -> %s (%s)' % (
op['type'], op['from'], op['to'], op['amount'],
)
....

if settings and message:
db.notifications.insert_one({
'username': settings['username'],
'email': settings['email'],
'telegram_channel_id': settings['telegram_channel_id'],
'message': message,
'email_sent': False,
'telegram_sent': False,
'created_at': datetime.utcnow(),
})

return True

嗯,我好像发现了一个了不起的BUG,一会再说。
这里又出来一个函数

1
2
3
4
5
6
def find_user_settings(username):
try:
rows = db.settings.find({'username': username, 'confirmed': True}).sort('created_at', -1)
return rows[0]
except Exception:
return dict()

这个函数的功能,就是按用户名,读取用户(已确认)的设置并返回。

1
2
3
4
if op['type'] == 'account_update':
settings = find_user_settings(op['account'])
if settings and settings['account_update']:
message = 'Received event: account_update (%s)' % op['account']

这样,这段代码就好理解了,按白话文翻译就是
如果操作是更新账户,
那么我就去设置表读取这个用户的设置
如果存在设置表中有这个用户的设置 并且 这个用户设置了关心 ‘account_update’
那么通知信息就是吧啦啦啦
是不是很好理解

因为操作只能是这些操作中的一种,所以要逐一判断一下,直到发现或者判断完毕没有发现为止。

1
2
3
4
5
6
7
8
9
10
11
12
if settings and message:
db.notifications.insert_one({
'username': settings['username'],
'email': settings['email'],
'telegram_channel_id': settings['telegram_channel_id'],
'message': message,
'email_sent': False,
'telegram_sent': False,
'created_at': datetime.utcnow(),
})

return True

如果发现了需要处理的操作,将其插入notifications 表。

BUG,BUG
注意这个 return True, 注意缩进,OMG,少缩进了一个TAB有木有?
这样,原本只有我们处理到的op 才应该返回True, 结果统统返回True了。

你问我后果是啥?貌似没啥后果,浪费一些数据库空间和CPU运算能力而已。

总结

  • Blockchain Worker 将blockchain上和账户有关的动态抓取进来
  • 判断是否是SteemData Notify 注册(并确认)用户相关的操作以及是否是用户关心的操作
  • 如果是,写入数据库通知表notifications

咦,一总结好像也没啥内容呢,那我为啥写了这么多呢?晕,这啰嗦的毛病要改一改了。
初学者水平有限,如有谬误敬请指正,深表谢意啦。


感谢阅读 / Thank you for reading.
欢迎upvote、resteem以及 following me @oflyhigh 😎


This page is synchronized from the post: SteemData Notify 代码学习一: Blockchain Worker / Code Study of SteemData Notify: Part one

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×