昨天在 Ruby 大会的微信群里聊起来物联网编程,就说今天写一篇。
硬件领域基本都是 C 语言或者汇编语言的天下,其他语言基本上也就是划划水的存在。
大家都知道,ruby 除了我们常用的 CRuby 版本外,还有 JRuby 等等各种实现版本,估计比较少人知道的是mruby。
直接使用mruby 还是有点难的,下面的下面会介绍一下。
Arduino 系列是针对硬件爱好者的开发的,通过这个项目mruby-arduino 可以用 Ruby 开发。
曾经试了一下,因为 Arduino 本身提供的语法也很简单,所以基本上就相当于玩了一下而已。
ESP 系列本身在工业领域用的比较多,现在也有一些类似于玩具一样的产品,比如说 ESP-EYE 等,可玩性很强。可以运行 TensorFlow Lite,实现离线人脸识别,除了速度慢没别的缺点。
有个开源的项目,mrubyc-esp32-arduino 可以实现在 ESP 系列硬件上用 ruby 编程,不过环境安装在国内实在是费劲。
但是 ESP 系列的可玩性很强,有个 micro-python 的项目,和 Ruby 相比起来更有优势。
另外还有针对BBC micro:bit v2的项目。
现在比较流行的有 NB-IoT 和 Lora 两种,当然还有其他的,有兴趣的可以自行百度一下。
NB-IoT,全称是 Narrow Band Internet of Things,窄带物联网,它的原型是 2014 年 5 月华为和沃达丰联合提出的 NB M2M 技术,随后在 2015 年 5 月与高通提出的 NB OFDMA 技术融合成为 NB-CIOT 技术,之后又于 2015 年 9 月与爱立信公司提出的 NB-LTE 技术融合,形成了我们现在认识的 NB-IoT,并在 3GPP 上正式立项。至 2016 年 6 月,NB-IoT 核心标准冻结,但相关特性仍在持续演进中。
NB-IoT 的优点是不需要自己组网,直接依托运营商网络就可以。缺点就很显然,数据都到了运营商手里了,当然也可以自行对数据进行加密。之前在一家公司负责和华强北对接,他们就对自己的数据进行了封装,运营商如果没有协议是很难自行破解的(其实也很简单,估计就是数据没什么价值,因为除了自定义协议外并没有进行数据加密)。
和华强北合作的项目用的是电信的 NB-IoT 平台,可以通过 REST 接口拉取,也可以用 Apache Pulsar Client SDK 自行拉取。
很多城市的电动车防盗就是通过 NB-IoT 实现的,利润你懂得~
国家准备在未来几年,健全 NB-IoT 网络,覆盖城乡农村,加强在农业等领域应用。
Lora 的好处是 Lora 所用的无线电频率连业余频率都不是,所以组建 Lora 网络不需要相关部门批准。缺点就是自己组网成本较高。
一台 Lora 发射器成本不到一千元,可以覆盖大约空旷地带 10 公里的范围,实际上受建筑物的影响,可能每层都需要一台中继,所以实现的成本还是很高的。
目前在燃气抄表等领域,都有应用 Lora 的案例,和 NB-IoT 相比应用比较小。
优点是完全自主可控。
这没啥好说的
我们使用的是EventMachine,好像之前停了一段时间之后又活了。
核心的原理就是物联网硬件通过 TCP Socket 发送到指定服务器,服务器通过 Nginx 做了一个负载均衡:
因为物联网硬件是通过 4G 网络连接的,所以物联网硬件的 IP 是动态的。为了保持服务器和硬件之间的连接,需要定时发送心跳。
然后通过EventMachine来保存连接,需要的时候发送指令等。
以下的代码好久没看了,仅供参考:)
require 'eventmachine'
class SaleServer < EM::Connection
include Wanfeng
extend Wanfeng
@@connections = {}
@@logger = Logger.new("#{Rails.root}/log/vending_machines.log")
def initialize
@@last_heart = Time.zone.now
end
def self.connections
@@connections
end
def self.deliver_goods_data(machine, row, column, count, password="111111")
sale(machine, rand(100), row, column, count, password).pack("C*")
end
def post_init
@@logger.info "connected "
end
def receive_data data
@received_data = data.unpack('H*').first
if valid_imei?
add_clients
log_received_data
show_connections
reply_heart
end
end
def unbind
@@connections.delete(self)
@@logger.info "#{imei} disconnected\n"
end
private
def imei
get_machine_code(@received_data)
end
def valid_imei?
!!(imei =~ /\A\d+\z/) && (imei&.size == 15)
end
def add_clients
$redis.hmset('vending_machines', imei, Time.zone.now )
@@connections[imei] = self
end
def log_received_data
last_time = "#{Time.now - @@last_heart} seconds"
@@last_heart = Time.now
@@logger.info "Received:\t#{last_time}\t#{@received_data}"
end
def reply_heart
unless imei.present?
@@connections[imei].send_data(heart(imei, "111111").pack("C*"))
end
end
def show_connections
@@connections.each do |k, v|
@@logger.info "Connection: #{k}: #{v} "
end
end
end
这个是一段控制售货机的代码,出货指令是通过ActiveJob
实现的:
class AutosaleJob < ApplicationJob
queue_as :default
def perform(machine, row, column, count)
SaleServer.connections[machine].send_data(
SaleServer.deliver_goods_data(machine, row, column, count)
)
end
end
如果用的是 Passenger 的话,需要在config/application.rb
中这样启动EventMachine
,没有这个if defined?(PhusionPassenger)
逻辑会报什么错,我已经忘了 :
# config/application.rb
...
config.after_initialize do
if defined?(PhusionPassenger)
PhusionPassenger.on_event(:starting_worker_process) do |forked|
if forked && EM.reactor_running?
EM.stop
end
Thread.new {
EM.run {
EM.start_server '0.0.0.0', 4000, SaleServer
}
}
Signal.trap("INT") { EM.stop }
Signal.trap("TERM") { EM.stop }
end
end
end
....
出货调用逻辑:
AutosaleJob.perform_now(params[:code], row, column, amount)
这样你就能在夏天喝上王老吉了:)
在开发单片机原型的时候,用 Ruby 快速验证是可以的,不过你必须要懂点 C 才行,不然寸步难行。
在服务器开发方面,用 Ruby 实现是没有问题,而且运行也非常稳定。
服务器性能优化,最重要的是找到影响性能的瓶颈。
曾经做过一个中国全海域风能评估的项目,绝大部分代码是用 Ruby 写的,只有统计的一小点用的是 C。
这样既满足了需求,也大大提高了开发的速度和效率。