Using Smpp in Ruby on Rails - ruby-on-rails

The need to send and receive SMS messages via Smpp, Register Number, received systemID and password, but fails to connect. Connected to the project gem 'ruby-smpp', to use the example of the getaway this, it only changed the values ​​systemID and password.
in the logs:
<- (BindTransceiver) len = 37 cmd = 9 status = 0 seq = 1364360797 (<systemID><password>)
Hex dump follows:
<- 00000000: 0000 0025 0000 0009 0000 0000 5152 7e5d | ...% ........ QR ~]
<- 00000010: 3531 3639 3030 0068 4649 6b4b 7d7d 7a00 | <systemID>.<password>.
<- 00000020: 0034 0001 00 | .4 ...
Starting enquire link timer (with 10s interval)
Delegate: transceiver unbound
in the console:
Connecting to SMSC ...
MT: Disconnected. Reconnecting in 5 seconds ..
MT: Disconnected. Reconnecting in 5 seconds ..
MT: Disconnected. Reconnecting in 5 seconds ..
MT: Disconnected. Reconnecting in 5 seconds ..
MT: Disconnected. Reconnecting in 5 seconds ..
Tell me, please, I do not, maybe in the config to something else to add or change? And smpp connection, as I understand, it works only with a specific IP-address, but the logs on the server and on the local machine are the same
class Gateway
include KeyboardHandler
# MT id counter.
##mt_id = 0
# expose SMPP transceiver's send_mt method
def self.send_mt(*args)
##mt_id += 1
##tx.send_mt(##mt_id, *args)
end
def logger
Smpp::Base.logger
end
def start(config)
# The transceiver sends MT messages to the SMSC. It needs a storage with Hash-like
# semantics to map SMSC message IDs to your own message IDs.
pdr_storage = {}
# Run EventMachine in loop so we can reconnect when the SMSC drops our connection.
puts "Connecting to SMSC..."
loop do
EventMachine::run do
##tx = EventMachine::connect(
config[:host],
config[:port],
Smpp::Transceiver,
config,
self # delegate that will receive callbacks on MOs and DRs and other events
)
print "MT: "
$stdout.flush
# Start consuming MT messages (in this case, from the console)
# Normally, you'd hook this up to a message queue such as Starling
# or ActiveMQ via STOMP.
EventMachine::open_keyboard(KeyboardHandler)
end
puts "Disconnected. Reconnecting in 5 seconds.."
sleep 5
end
end
# ruby-smpp delegate methods
def mo_received(transceiver, pdu)
logger.info "Delegate: mo_received: from #{pdu.source_addr} to #{pdu.destination_addr}: #{pdu.short_message}"
end
def delivery_report_received(transceiver, pdu)
logger.info "Delegate: delivery_report_received: ref #{pdu.msg_reference} stat #{pdu.stat}"
end
def message_accepted(transceiver, mt_message_id, pdu)
logger.info "Delegate: message_accepted: id #{mt_message_id} smsc ref id: #{pdu.message_id}"
end
def message_rejected(transceiver, mt_message_id, pdu)
logger.info "Delegate: message_rejected: id #{mt_message_id} smsc ref id: #{pdu.message_id}"
end
def bound(transceiver)
logger.info "Delegate: transceiver bound"
end
def unbound(transceiver)
logger.info "Delegate: transceiver unbound"
EventMachine::stop_event_loop
end
end
module KeyboardHandler
include EventMachine::Protocols::LineText2
def receive_line(data)
sender, receiver, *body_parts = data.split
unless sender && receiver && body_parts.size > 0
puts "Syntax: <sender> <receiver> <message body>"
else
body = body_parts.join(' ')
puts "Sending MT from #{sender} to #{receiver}: #{body}"
SampleGateway.send_mt(sender, receiver, body)
end
prompt
end
def prompt
print "MT: "
$stdout.flush
end
end
/initializers
require 'eventmachine'
require 'smpp'
LOGFILE = Rails.root + "log/sms_gateway.log"
Smpp::Base.logger = Logger.new(LOGFILE)
/script
puts "Starting SMS Gateway. Please check the log at #{LOGFILE}"
config = {
:host => '127.0.0.1',
:port => 6000,
:system_id => <SystemID>,
:password => <Password>,
:system_type => '', # default given according to SMPP 3.4 Spec
:interface_version => 52,
:source_ton => 0,
:source_npi => 1,
:destination_ton => 1,
:destination_npi => 1,
:source_address_range => '',
:destination_address_range => '',
:enquire_link_delay_secs => 10
}
gw = Gateway.new
gw.start(config)
file from the script / run through the rails runner

Problem solved. Initially were incorrectly specified host and port, as smpp-server was not on the same machine as the RoR-application. As for encoding, sending in Russian layout should be
message = text.encode ("UTF-16BE"). force_encoding ("BINARY")
Gateway.send_mt (sender, receiver, message, data_coding: 8)
To receive a form in the right
message = pdu.short_message.force_encoding ('UTF-16BE'). encode ('UTF-8')

Related

Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction (Ruby on Rails)

I have an issue with my Ruby on Rails application. So, there are workers, which are listening for different rabbitMQ topics. Each worker make some changes it it's DB (mariaDB) table and in the end update 'last_connection' field in common object 'device', where I have issue.
This is one worker:
include Sneakers::Worker
# This worker will connect to "queue" queue
# env is set to nil since by default the actuall queue name would be
# "queue_development"
from_queue "sensor_log"
# work method receives message payload in raw format
def work(raw_message)
logger.info ("sensor_log " + raw_message)
msg = JSON.parse(raw_message)
# msg = {"deviceId" => 102,"timestamp" => 1487318555,"sensor" => 5, "values" => [1,2,3,4,5,6,7,8], "isNewSensor" => false, "logDelayed" => false}
begin
#device = Device.find(msg["deviceId"])
ActiveRecord::Base.transaction do
# Initialize
timestamp = Time.at(msg["timestamp"])
MiddlewareLog.create(
device_id: msg["deviceId"],
message: JSON.pretty_generate(msg),
queue: MiddlewareLog.queues[:sensor_log]
)
if(msg["ext_brd_type"] == 6)
logger.info ("Logging external sensors lm2, lm4, lm4")
# Logging external sensors lm2, lm4, lm4
Sensors::SENSORS_EXT.map do |code|
SensorLog.create(
device_id: msg["deviceId"],
sensor: code,
state: msg[code],
value: msg[code],
is_delayed: msg["logDelayed"],
created_at: timestamp
)
end
else
logger.info ("Logging native sensors")
# Logging native
device_sensors = #device.new_version? ? Sensors::SENSORS_CODES_NEW : Sensors::SENSORS_CODES
#sensors = device_sensors.reject{|code, sensor| code & msg["sensor"] == 0}
#sensors.map do |code, sensor|
SensorLog.create(
device_id: msg["deviceId"],
sensor: sensor[:name],
state: sensor[:state],
value: msg["values"][sensor[:bit_position]],
is_delayed: msg["logDelayed"],
created_at: timestamp
)
end
Rollbar.warning("Unknown device sensor", :message => msg, :sensor => msg["sensor"]) if #sensors.empty?
#device.update_sensors_state(msg["values"]) if #sensors.any?
end
# Avoid updated_at deadlock
#device.save!(touch: false)
end
# Touch updated_at and last_connection_at
#device.touch(:last_connection_at)
ack! # we need to let queue know that message was received
rescue => exception
logger.error ("sensors_log exception:")
logger.error exception
Rollbar.error(exception, :message => msg)
requeue!
end
end
end
Here is the second one:
class SystemLogsWorker
include Sneakers::Worker
# This worker will connect to "queue" queue
# env is set to nil since by default the actuall queue name would be
# "queue_development"
from_queue "system_log"
# #logger = Logger.new(STDOUT)
# #logger.level = Logger::INFO
# work method receives message payload in raw format
def work(raw_message)
# #logger.info raw_message
logger.info ("system_log " + raw_message)
msg = JSON.parse(raw_message)
# msg = {"deviceId":102,"timestamp":1487318555,"system":2069,"logDelayed":false,"fault_code":1}
begin
#device = Device.find(msg["deviceId"])
ActiveRecord::Base.transaction do
# Initialize
timestamp = Time.at(msg["timestamp"])
MiddlewareLog.create(
device_id: msg["deviceId"],
message: JSON.pretty_generate(msg),
queue: MiddlewareLog.queues[:system_log]
)
#system = Systems::EVENTS_CODES[msg["system"]]
# #logger.warn("Unknown device system", :message => msg, :system => msg[:system]) unless #system
# logger.warn("Unknown device system", :message => msg, :system => msg["system"]) unless #system
# Rollbar.warning("Unknown device system", :message => msg, :system => msg["system"]) unless #system
logger.warn("Unknown device system. Message:" + raw_message) unless #system
Rollbar.warning("Unknown device system. Message:" + raw_message) unless #system
# Loggin
system_log = SystemLog.create(
device_id: msg["deviceId"],
system: #system[:name],
state: #system[:state],
is_delayed: msg["logDelayed"],
fault_code: msg["fault_code"],
created_at: timestamp
) if #system
#device.update_systems_state(system_log) if #system
# Avoid updated_at deadlock
#device.save!(touch: false)
end
# Touch updated_at and last_connection_at
#device.touch(:last_connection_at)
ack! # we need to let queue know that message was received
rescue => exception
logger.error ("system_log exception:")
logger.error exception
Rollbar.error(exception, :message => msg)
requeue!
end
end
end
In the runtime I get the message:
2020-06-18T11:09:08Z p-13299 t-gmvtsrzac ERROR: sensors_log exception:
2020-06-18T11:09:08Z p-13299 t-gmvtsrzac ERROR: Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction: UPDATE devices SET devices.updated_at = '2020-06-18 11:09:08', devices.last_connection_at = '2020-06-18 11:09:08' WHERE devices.id = 3024
2020-06-18T11:09:08Z p-13299 t-gmvtsq74w ERROR: system_log exception:
2020-06-18T11:09:08Z p-13299 t-gmvtsq74w ERROR: Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction: UPDATE devices SET devices.updated_at = '2020-06-18 11:09:08', devices.last_connection_at = '2020-06-18 11:09:08' WHERE devices.id = 3024
I think that the problem point in
#device.touch(:last_connection_at), because in one time both workers trying to update one table row.
I'm not so good with ruby and will be glad to any help with this.
Have you tried using a lock within the transaction before updating the database data?
#device.lock!
#device.save!(touch: false)
#device.touch(:last_connection_at)
You can also start a lock and transaction at the same time using with_lock:
#device = Device.find(msg["deviceId"])
#device.with_lock do
# your block here
end
As described in https://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html

Rails + Resque: Error not going away after update

I have a background job being run by Resque and I the process begins correctly but then I see the error:
Error
undefined method `path' for #<Hash:0x007f1900c25298>
/app/app/models/weigh_in.rb:373:in `import_without_check'
/app/app/jobs/uploads.rb:8:in `perform'
and initially this was an error but I've since updated my file and completely removed this line, pushed to heroku, restarted heroku as well as redis, yet still the error persists.
Here's the function being called in app/models/weigh_in:
def self.import_without_check(file, location_id)
error = []
success = []
options = {:key_mapping => #new_hash, :strings_as_keys => true, :keep_original_headers => true, :remove_unmapped_keys => true}
SmarterCSV.process(file, options) do |row|
hashed_row = row[0]
next if hashed_row[:scale_id].blank? || hashed_row[:scale_id].nil?
hashed_row[:unique_scale] = location_id + hashed_row[:scale_id].to_s
hashed_row = hashed_row.to_hash.except!(nil).as_json
p hashed_row
client = Client.select('id', 'name').find_by(unique_scale: hashed_row['unique_scale']) || Client.select('id', 'name').find_by(unique_mb: hashed_row['unique_scale'])
if client.nil?
error << hashed_row
next
end
hashed_row['client_id'] = client.id
program_id = client.programs.last.id
if program_id.nil? || hashed_row['client_id'].nil?
error << hashed_row
next
end
check_in = CheckIn.new(client_id: client.id, type_of_weighin: 'Standard', program_id: program_id)
unless hashed_row['date'].blank? || hashed_row['date'].nil?
p 'date', hashed_row['date']
hashed_row['date'] = Date.strptime(hashed_row["date"], "%m/%d/%y").strftime()
end
hashed_row.except!("unique_scale")
if check_in.save
hashed_row['check_in_id'] = check_in.id
end
if hashed_row['check_in_id'].nil?
error << hashed_row
next
end
weigh_in = WeighIn.new(hashed_row)
p weigh_in.valid?, weigh_in.errors.full_messages, weigh_in.errors
if weigh_in.save
success << hashed_row
end
end
return success, error
end
Is there something I need to do to make this error go away?
I solved this issue by connecting to the redis Heroku add-on via it's cli, then getting its clients, killing all connections, and restarting my heroku dynos. Once this was done, my changes were recorded:
$ heroku redis:cli
21579> client list # Returns clients
21579> client kill 10.159.107.79:43434 # use number from addr property returned with client list command
21579> quit # Exit cli
$ heroku restart
I hope this helps someone else

[RUBY]Server/Client | Command Client to start Using function

So I am working on some stupid simulation of mining cryptocoins - just for fun.
But I have run into an issue I want to command the client to start using a function but I am not sure how to do it, my code is bellow for Server.rb and Client.rb
Client
require 'socket'
s = TCPSocket.new 'localhost', 2626
while line = s.gets
puts line
end
s.close
Server.rb
#!/bin/ruby
require "socket"
require 'securerandom'
PORT = 2626
server_pool = TCPServer.open(PORT)
sndc_block_time = 200 # Size of block
sndc_block_time = 10 # Time to brake block 1,1 out of 200, 200
sndc_coin_blocks= 126^2 # Ammount of available blocks
sndc_coin_balance = 0
sub_blocks = 0 # Sub blocks user wants to mine
addr = SecureRandom.hex # generate a random hexadecimal address
# Notify message shown when user registers a new address
disclaimer = "Welcome to the SendCoin Network!
A new address has been registered and saved
to your computer desktop!"
def handle_connection(client)
puts "New client! #{client} \n\n"
end
puts "Listening on #{PORT}. Press CTRL+C to cancel."
while client = server_pool.accept
if Thread.new { handle_connection(client) }
if sndc_coin_blocks < 126^2 / 4
sndc_coin_blocks + 126^2 / 4 # Add a quarter of the original size
end
if Dir['../MyAddress/*.addr'].any? == true
# Statrs Mining
client.puts "Starting to mine.."
sleep(5)
client.puts "Current blocks: " + sndc_coin_blocks.to_s
client.puts "Block Size: " + sndc_coin_balance.to_s
client.puts "Approximate time to mine 1 sub-block: " + sndc_block_time.to_s
client.puts "Searching for block.."
sleep(3)
if sndc_block_time != 10
sndc_block_time = 10
end
client.puts "Started mining..."
elsif Dir['../addresses/*.addr'].any? == false
File.open("../addresses/"+addr+".addr", "w") do |f| # Create file
f.write(sndc_coin_balance.to_s)
end
client.puts "New address generated: " + addr.to_s + " , you may realunch the app now and enter your address!"
# Start Mining
puts "> File 'address.addr' has been generated for#{client}"
end
end
end
So essentially I want after this line of code:
client.puts "Started mining..."
.. to command the client to (client.rb) to start using a function.

Class variable in new thread not in scope

I am having an issue with the following and that might be straightforward to someone with more knowledge or experience. Having spent the whole weekend trying to figure it out without success...extra help and insight would be very appreciated, thank you.
I have a class variable, ##clients that keeps track of opened websocket connections. When I access that variable from within the on.message block from the redis_sub.subscribe block (in a new Thread) the array is empty. I made a test class variable ##test that is incremented everytime a new websocket connection happens and log that variable, same, the log shows ##test to be 0, its initial value.
class Shrimp
# Heroku has a 50 seconds idle connection time limit.
KEEPALIVE_TIME = 15 # in seconds
##clients = []
##test = 0
class << self
def load_session(client)
if !client.env["rack.session"].loaded?
client.env["rack.session"][:init] = true
end
end
def get_client(user_id)
# check if user is one of the ws clients
##clients.each do |client|
# lazily load session
load_session(client)
# get user_id from the session
if(client.env["rack.session"]["warden.user.user.key"][0][0] == user_id)
return client
end
end
nil
end
end
def initialize(app)
#app = app
redis_uri = URI.parse(ENV["REDISCLOUD_URL"])
# work on a separte thread not to block current thread
Thread.new do
redis_sub = Redis.new(host: redis_uri.host, port: redis_uri.port, password: redis_uri.password)
redis_sub.subscribe(ENV["REDIS_CHANNEL"]) do |on| # thread blocking operation
p [:redis_suscribe]
p [:test, ##test]
on.message do |channel, msg|
data = JSON.parse(msg)
p [:redis_receive_message, data]
p [:test, ##test]
client = Shrimp.get_client(data["user_id"])
client.send(data["thumbnail_urls"].to_json) if client
end
end
end
end
def call(env)
env['rack.shrimp'] = self
if Faye::WebSocket.websocket?(env)
puts websocket_string
# Send every KEEPALIVE_TIME sec a ping for keeping the connection open.
ws = Faye::WebSocket.new(env, nil, { ping: KEEPALIVE_TIME })
ws.on :open do |event|
puts '***** WS OPEN *****'
##clients << ws
##test = ##test + 1
p [:test, ##test]
end
ws.on :message do |event|
puts '***** WS INCOMING MESSAGE *****'
p [:message, event.data]
p [:test, ##test]
##clients.each { |client| client.send(event.data.to_json) }
end
ws.on :close do |event|
puts '***** WS CLOSE *****'
p [:close, ws.object_id, event.code, event.reason]
##clients.delete(ws)
p [:test, ##test]
ws = nil
end
ws.on :error do |event|
puts '***** WS ERROR *****'
p [:close, ws.object_id, event.code, event.reason]
end
# Return async Rack response
ws.rack_response
else
#app.call(env)
end
end
end
terminal output:
we can see the ##testclass variable being 1 after a first connection opens, still 1 when the server gets a message from that one client, 0 when logged from within the on.message block and 1 again when that websocket connection is closed.
I am obviously missing something but cannot figure it out despite all the research and readings.

How to send a keep-alive packet through websocket in ruby on rails

I want to send a
"Keep alive from client"
message every 30 seconds for my websocket connection. Here's what the code that I have in my websocket initializer looks like:
ws = WebSocket::Client::Simple.connect 'wss://bitcoin.toshi.io/'
ws.on :message do |msg|
rawJson = msg.data
message_response = JSON.parse(rawJson)
end
ws.on :open do
ws.send "{\"subscribe\":\"blocks\"}"
end
ws.on :close do |e|
puts "WEBSOCKET HAS CLOSED #{e}"
exit 1
end
ws.on :error do |e|
puts "WEBSOCKET ERROR #{e}"
end
Without any sort of 'keep alive', the connect closes in about 45 seconds. How should I send the 'heart-beat' packet? It seems that the connection is closed by their server, not mine.
You can use Websocket Eventmachine Client gem to send hearbeat:
require 'websocket-eventmachine-client'
EM.run do
ws = WebSocket::EventMachine::Client.connect(:uri => 'wss://bitcoin.toshi.io/')
puts ws.comm_inactivity_timeout
ws.onopen do
puts "Connected"
end
ws.onmessage do |msg, type|
puts "Received message: #{msg}"
end
ws.onclose do |code, reason|
puts "Disconnected with status code: #{code}"
end
EventMachine.add_periodic_timer(15) do
ws.send "{}"
end
end
You can setup timer for EventMachine with EM::add_periodic_timer(interval_in_seconds), and then send your heartbeat with it.
You can use the auto-ping feature (its default and can't be turned off) if you're using Iodine's Websocket client:
require 'iodine/http'
# prevents the Iodine's server from running
Iodine.protocol = :timer
# starts Iodine while the script is still running
Iodine.force_start!
# set pinging to a 40 seconds interval.
Iodine::Http::Websockets.default_timeout = 40
settings = {}
# set's the #on_open event callback.
settings[:on_open] = Proc.new do
write 'sending this connection string.'
end
# set's the #on_message(data) event callback.
settings[:on_message] = Proc.new { |data| puts "Received message: #{data}" }
# connects to the websocket
Iodine::Http.ws_connect 'ws://localhost:8080', settings
It's a fairly basic client, but also easy to manage.
EDIT
Iodine also includes some cookie and custom header's support, as now seen in Iodine's documentation. So it's possible to use different authentication techniques (authentication headers or cookies).

Resources